为什么不鼓励接受对 String (&String)、Vec (&Vec) 或 Box (&Box) 的引用作为函数参数?

Why is it discouraged to accept a reference to a String (&String), Vec (&Vec), or Box (&Box) as a function argument?

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

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

我还编写了接受对 VecBox:

的引用的代码
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 以允许使用更通用的代码。


  1. 使用 StringVec 的主要原因之一是它们允许增加或减少容量。但是,当您接受不可变引用时,您不能在 VecString.

    上使用任何这些有趣的方法
  2. 接受&String&Vec&Box需要之前在堆上分配参数你可以调用这个函数。接受 &str 允许字符串文字(保存在程序数据中)并接受 &[T]&T 允许堆栈分配的数组或变量。不必要的分配是一种性能损失。当您尝试在测试或 main 方法中调用这些方法时,这通常会立即暴露出来:

    awesome_greeting(&String::from("Anna"));
    
    total_price(&vec![42, 13, 1337])
    
    is_even(&Box::new(42))
    
  3. 另一个性能考虑因素是 &String&Vec&Box 引入了一个不必要的间接层,因为您必须取消对 &String 的引用得到一个 String 然后执行第二次解引用以结束在 &str.

相反,您应该接受一个 字符串切片 (&str),一个 切片 (&[T]) ,或只是参考 (&T)。 &String&Vec<T>&Box<T> 将自动(通过 deref coercion)转换为 &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
}

现在您可以使用更多类型调用这些方法。例如,可以使用字符串文字 ("Anna") 分配的 String 调用 awesome_greetingtotal_price 可以通过引用数组 (&[1, 2, 3]) 分配的 Vec.

来调用

如果您想在 StringVec<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();
    }
}

除了之外,接受&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让你的函数调用更加统一方便,"easiest"方式现在也是最高效的。这些示例也适用于 Cow<[T]>