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

She*_*ter 100 string reference rust borrowing

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

fn awesome_greeting(name: &String) {
    println!("Wow, you are awesome, {}!", name);
}
Run Code Online (Sandbox Code Playgroud)

我还编写了代码来引用a VecBox:

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

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

但是,我收到一些反馈意见,这样做并不是一个好主意.为什么不?

She*_*ter 131

TL; DR:可以改为使用&str,&[T]&T可以不丢失通用性.


  1. 使用a String或a的主要原因之一Vec是因为它们允许增加或减少容量.但是,当您接受不可变引用时,您不能在Vec或使用任何有趣的方法String.

  2. 接受&String,&Vec或者&Box需要一个配置,然后才能调用该方法.不必要的分配是性能损失.当您尝试在测试或方法中调用这些方法时,通常会立即显示此信息&str:

    awesome_greeting(&String::from("Anna"));
    
    Run Code Online (Sandbox Code Playgroud)
    total_price(&vec![42, 13, 1337])
    
    Run Code Online (Sandbox Code Playgroud)
    is_even(&Box::new(42))
    
    Run Code Online (Sandbox Code Playgroud)
  3. 另一个性能考虑因素是&[T],&Tmain引入一个不必要的间接层,因为你已经取消引用&String,得到一个&Vec,然后第二个引用结束&Box.

相反,您应该接受字符串slice(&String),slice(String)或仅引用(&str).甲&str,&[T]&T将被自动强制转换为&String,&Vec<T>&Box<T>分别.

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

现在,您可以使用更广泛的类型集来调用这些方法.例如,&str可以使用字符串literal(&[T])已分配来调用&T.awesome_greeting可以通过引用array("Anna")已分配来调用String.


如果你想添加或删除项目total_price或者&[1, 2, 3],你可以采取一个可变的引用(VecString):

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

特别是对于切片,您也可以接受Vec<T>&mut String.这允许您改变切片内的特定值,但是您无法更改切片内的项目数(这意味着它对字符串非常有限):

fn reset_first_price(prices: &mut [i32]) {
    prices[0] = 0;
}
Run Code Online (Sandbox Code Playgroud)

fn lowercase_first_ascii_character(s: &mut str) {
    if let Some(f) = s.get_mut(0..1) {
        f.make_ascii_lowercase();
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 一开始怎么样?dr**?这个答案已经有点长了.像"&str"这样的东西更普遍(如:在没有减少限制的情况下施加更少的限制)"?另外:我认为第3点通常不那么重要.通常`Vec`s和`String`s将存在于堆栈中,甚至经常在当前堆栈帧附近.堆栈通常很热,取消引用将从CPU缓存中提供. (5认同)
  • 为了完整起见,应该注意的是,接受“&amp;String”或“&amp;Vec”唯一有意义的时候是当您需要访问仅存在于这些方法(即“capacity”)上的“&amp;self”方法时。此外,这些都不适用于“可变”借用,即“&amp;mut Vec”或“&amp;mut String”,当您想要增大或缩小集合时,它们是合法需要的。 (5认同)
  • 我缺少有关为什么需要额外分配的信息。字符串存储在堆上,当接受 &amp;String 作为参数时,为什么不只是 Rust 传递存储在堆栈上的指向堆空间的指针,我不明白为什么传递 &amp;String 需要额外的分配,传递一个字符串切片还应该需要发送存储在堆栈上的指向堆空间的指针吗? (4认同)
  • @Shepmaster:关于分配成本,在讨论强制分配时,可能值得一提的是子串/切片的特定问题.`total_price(&prices [0..4])`不需要为切片分配新的向量. (3认同)
  • 这是一个很好的答案。我刚开始在Rust中工作,并被搞清楚何时应该使用`&str`和* why *(来自Python,所以我通常不明确处理类型)。完美地清除了所有这些 (2认同)
  • 关于参数的很棒的提示。只需要一个疑问: *“接受一个 &amp;String、&amp;Vec 或 &amp;Box 也需要分配才能调用该方法。”* ...为什么会这样?您能否指出文档中我可以详细阅读的部分?(我是初学者)。另外,我们可以在返回类型上有类似的提示吗? (2认同)
  • @cjohansson 你提到的堆上的存储*是*额外的分配。引用本身按照您的描述传递。`awesome_greeting(&amp;String::from(“Anna”))` 根本不需要创建字符串的分配;`awesome_greeting(“Anna”)` 没有。像这样的字符串文字根本不存储在堆中。 (2认同)

Pet*_*all 18

除了Shepmaster的回答之外,接受a &str(以及类似的&[T]等)的另一个原因是因为除了 所有其他类型String并且&str也满足Deref<Target = str>.其中一个最值得注意的例子是Cow<str>,它让您可以非常灵活地处理自有或借用的数据.

如果你有:

fn awesome_greeting(name: &String) {
    println!("Wow, you are awesome, {}!", name);
}
Run Code Online (Sandbox Code Playgroud)

但你需要用a调用它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());
Run Code Online (Sandbox Code Playgroud)

当您将参数类型更改为时&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);
Run Code Online (Sandbox Code Playgroud)

接受&str使得调用您的功能更加统一和方便,而"最简单"的方式现在也是最有效的.这些例子也适用于Cow<[T]>等.