我写了一些Rust代码,以&String作为参数:

fn awesome_greeting(name: &String) {
    println!("Wow, you are awesome, {}!", name);
}

我还写了一些代码来引用Vec或Box:

fn total_price(prices: &Vec<i32>) -> i32 {
    prices.iter().sum()
}

fn is_even(value: &Box<i32>) -> bool {
    **value % 2 == 0
}

然而,我收到了一些反馈,说这样做不是一个好主意。为什么不呢?


当前回答

TL;DR:可以使用&str, &[T]或&T来实现更通用的代码。


One of the main reasons to use a String or a Vec is because they allow increasing or decreasing the capacity. However, when you accept an immutable reference, you cannot use any of those interesting methods on the Vec or String. Accepting a &String, &Vec or &Box also requires the argument to be allocated on the heap before you can call the function. Accepting a &str allows a string literal (saved in the program data) and accepting a &[T] or &T allows a stack-allocated array or variable. Unnecessary allocation is a performance loss. This is usually exposed right away when you try to call these methods in a test or a main method: awesome_greeting(&String::from("Anna")); total_price(&vec![42, 13, 1337]) is_even(&Box::new(42)) Another performance consideration is that &String, &Vec and &Box introduce an unnecessary layer of indirection as you have to dereference the &String to get a String and then perform a second dereference to end up at &str.

相反,你应该接受一个字符串切片(&str),一个切片(&[T]),或者一个引用(&T)。& string, & vec <T>或& box <T>将分别自动强制(通过deref强制)为&str, &[T]或&T。

fn awesome_greeting(name: &str) {
    println!("Wow, you are awesome, {}!", name);
}
fn total_price(prices: &[i32]) -> i32 {
    prices.iter().sum()
}
fn is_even(value: &i32) -> bool {
    *value % 2 == 0
}

现在您可以使用更广泛的类型集调用这些方法。例如,awesome_greeting可以用一个字符串文字(“Anna”)或一个分配的字符串来调用。total_price可以通过引用数组(&[1,2,3])或分配的Vec来调用。


如果你想从String或Vec<T>中添加或删除项,你可以采用一个可变引用(&mut String或&mut Vec<T>):

fn add_greeting_target(greeting: &mut String) {
    greeting.push_str("world!");
}
fn add_candy_prices(prices: &mut Vec<i32>) {
    prices.push(5);
    prices.push(25);
}

特别是对于切片,你也可以接受&mut [T]或&mut str。这允许你在切片内改变一个特定的值,但你不能改变切片内的项目数量(这意味着它对字符串非常限制):

fn reset_first_price(prices: &mut [i32]) {
    prices[0] = 0;
}
fn lowercase_first_ascii_character(s: &mut str) {
    if let Some(f) = s.get_mut(0..1) {
        f.make_ascii_lowercase();
    }
}

其他回答

TL;DR:可以使用&str, &[T]或&T来实现更通用的代码。


One of the main reasons to use a String or a Vec is because they allow increasing or decreasing the capacity. However, when you accept an immutable reference, you cannot use any of those interesting methods on the Vec or String. Accepting a &String, &Vec or &Box also requires the argument to be allocated on the heap before you can call the function. Accepting a &str allows a string literal (saved in the program data) and accepting a &[T] or &T allows a stack-allocated array or variable. Unnecessary allocation is a performance loss. This is usually exposed right away when you try to call these methods in a test or a main method: awesome_greeting(&String::from("Anna")); total_price(&vec![42, 13, 1337]) is_even(&Box::new(42)) Another performance consideration is that &String, &Vec and &Box introduce an unnecessary layer of indirection as you have to dereference the &String to get a String and then perform a second dereference to end up at &str.

相反,你应该接受一个字符串切片(&str),一个切片(&[T]),或者一个引用(&T)。& string, & vec <T>或& box <T>将分别自动强制(通过deref强制)为&str, &[T]或&T。

fn awesome_greeting(name: &str) {
    println!("Wow, you are awesome, {}!", name);
}
fn total_price(prices: &[i32]) -> i32 {
    prices.iter().sum()
}
fn is_even(value: &i32) -> bool {
    *value % 2 == 0
}

现在您可以使用更广泛的类型集调用这些方法。例如,awesome_greeting可以用一个字符串文字(“Anna”)或一个分配的字符串来调用。total_price可以通过引用数组(&[1,2,3])或分配的Vec来调用。


如果你想从String或Vec<T>中添加或删除项,你可以采用一个可变引用(&mut String或&mut Vec<T>):

fn add_greeting_target(greeting: &mut String) {
    greeting.push_str("world!");
}
fn add_candy_prices(prices: &mut Vec<i32>) {
    prices.push(5);
    prices.push(25);
}

特别是对于切片,你也可以接受&mut [T]或&mut str。这允许你在切片内改变一个特定的值,但你不能改变切片内的项目数量(这意味着它对字符串非常限制):

fn reset_first_price(prices: &mut [i32]) {
    prices[0] = 0;
}
fn lowercase_first_ascii_character(s: &mut str) {
    if let Some(f) = s.get_mut(0..1) {
        f.make_ascii_lowercase();
    }
}

除了Shepmaster的答案,接受&str(类似的&[T]等)的另一个原因是,除了String和&str,所有其他类型也满足Deref<Target = str>。最著名的例子之一是Cow<str>,它允许您非常灵活地处理拥有的数据还是借来的数据。

如果你有:

fn awesome_greeting(name: &String) {
    println!("Wow, you are awesome, {}!", name);
}

但是你需要用Cow<str>调用它,你必须这样做:

let c: Cow<str> = Cow::from("hello");
// Allocate an owned String from a str reference and then makes a reference to it anyway!
awesome_greeting(&c.to_string());

当你将参数类型更改为&str时,你可以无缝地使用Cow,而无需任何不必要的分配,就像使用String一样:

let c: Cow<str> = Cow::from("hello");
// Just pass the same reference along
awesome_greeting(&c);

let c: Cow<str> = Cow::from(String::from("hello"));
// Pass a reference to the owned string that you already have
awesome_greeting(&c);

接受&str使函数调用更加统一和方便,而且“最简单”的方法现在也是最有效的方法。这些例子也适用于Cow<[T]>等。

建议使用&str而不是&String,因为&str也满足&String,它既可以用于拥有的字符串,也可以用于字符串切片,但不能反过来使用:

use std::borrow::Cow;

fn greeting_one(name: &String) {
    println!("Wow, you are awesome, {}!", name);
}

fn greeting_two(name: &str) {
    println!("Wow, you are awesome, {}!", name);
}

fn main() {
    let s1 = "John Doe".to_string();
    let s2 = "Jenny Doe";
    let s3 = Cow::Borrowed("Sally Doe");
    let s4 = Cow::Owned("Sally Doe".to_string());

    greeting_one(&s1);
    // greeting_one(&s2);  // Does not compile
    // greeting_one(&s3);  // Does not compile
    greeting_one(&s4);
    
    greeting_two(&s1);
    greeting_two(s2);
    greeting_two(&s3);
    greeting_two(&s4);
}

使用向量来操作文本从来都不是一个好主意,甚至不值得讨论,因为您将失去所有的健全性检查和性能优化。字符串类型在内部使用vector。记住,为了提高存储效率,Rust对字符串使用UTF-8。如果你用向量,你必须重复所有艰难的工作。除此之外,借用向量或盒装值应该是可以的。

因为这些类型可以被强制,所以如果我们使用这些类型,函数将接受更少的类型:

1- String的引用可以被强制转换为str切片。例如,创建一个函数:

fn count_wovels(words:&String)->usize{
    let wovels_count=words.chars().into_iter().filter(|x|(*x=='a') | (*x=='e')| (*x=='i')| (*x=='o')|(*x=='u')).count();
    wovels_count
}

如果你通过&str,它将不被接受:

let name="yilmaz".to_string();
println!("{}",count_wovels(&name));
// this is not allowed because argument should be &String but we are passing str
// println!("{}",wovels("yilmaz"))

但如果该函数接受&str

// words:&str
fn count_wovels(words:&str)->usize{ ... }

我们可以把这两种类型都传递给函数

let name="yilmaz".to_string();
println!("{}",count_wovels(&name));
println!("{}",wovels("yilmaz"))

这样,我们的函数就可以接受更多类型

2-类似的,对Box &Box[T]的引用,将被强制引用到Box Box[&T]内的值。例如

fn length(name:&Box<&str>){
    println!("lenght  {}",name.len())
}

这只接受&Box<&str>类型

let boxed_str=Box::new("Hello");
length(&boxed_str);

// expected reference `&Box<&str>` found reference `&'static str`
// length("hello")

如果将&str作为类型传递,则可以同时传递两个类型

Vec的ref和数组的ref之间存在类似的关系

fn square(nums:&Vec<i32>){
    for num in nums{
        println!("square of {} is {}",num,num*num)
    }
}
fn main(){
    let nums=vec![1,2,3,4,5];
    let nums_array=[1,2,3,4,5];
    // only &Vec<i32> is accepted
    square(&nums);
    // mismatched types: mismatched types expected reference `&Vec<i32>` found reference `&[{integer}; 5]`
    //square(&nums_array)
}

这对两种类型都适用

fn square(nums:&[i32]){..}