rust 为什么不建议接受对字符串(&String)、Vec(&Vec)或框(&Box)的引用作为函数参数?

rks48beu  于 2022-11-24  发布在  其他
关注(0)|答案(3)|浏览(353)

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

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
}

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

zvms9eto

zvms9eto1#

TL;DR:可以使用&str&[T]&T来允许更通用的代码。

1.使用StringVec的一个主要原因是它们允许增加或减少容量。然而,当你接受一个不可变的引用时,你就不能在VecString上使用这些有趣的方法。
1.接受&String&Vec&Box要求在调用函数之前在堆上分配参数。接受&str允许字符串文字(保存在程序数据中)并接受&[T]&T时,允许堆栈分配数组或变量。不必要的分配会导致性能损失。当您尝试在测试或main方法中调用这些方法时,通常会立即公开这些方法:
第一个
1.另一个性能考虑因素是&String&Vec&Box引入了不必要的间接层,因为您必须取消引用&String以获得String,然后执行第二次取消引用以在&str处结束。
相反,您应该接受 * 字符串切片 &str)、 切片 *(&[T])或仅接受引用(&T)。&String&Vec<T>&Box<T>将分别自动强制(通过 deref强制 )为&str&[T]&T
第一个
现在,您可以使用更广泛的类型集来调用这些方法。例如,可以使用字符串文字("Anna"
或 * 分配的String来调用awesome_greeting。可以使用对数组(&[1, 2, 3]
或 * 分配的Vec的引用来调用total_price
如果您想在StringVec<T>中添加或删除项,可以使用 * 可变引用 *(&mut String&mut Vec<T>):
第一个
特别是对于切片,你也可以接受&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();
    }
}
q8l4jmvw

q8l4jmvw2#

除了Shepmaster's answer之外,接受&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]>等。

t0ybt7op

t0ybt7op3#

建议使用&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);
}

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

相关问题