为什么不鼓励接受对 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);
}
我还编写了接受对 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
以允许使用更通用的代码。
使用 String
或 Vec
的主要原因之一是它们允许增加或减少容量。但是,当您接受不可变引用时,您不能在 Vec
或 String
.
上使用任何这些有趣的方法
接受&String
、&Vec
或&Box
还需要之前在堆上分配参数你可以调用这个函数。接受 &str
允许字符串文字(保存在程序数据中)并接受 &[T]
或 &T
允许堆栈分配的数组或变量。不必要的分配是一种性能损失。当您尝试在测试或 main
方法中调用这些方法时,这通常会立即暴露出来:
awesome_greeting(&String::from("Anna"));
total_price(&vec![42, 13, 1337])
is_even(&Box::new(42))
另一个性能考虑因素是 &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_greeting
。 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();
}
}
除了之外,接受&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]>
等
我写了一些以 &String
作为参数的 Rust 代码:
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
以允许使用更通用的代码。
使用
上使用任何这些有趣的方法String
或Vec
的主要原因之一是它们允许增加或减少容量。但是,当您接受不可变引用时,您不能在Vec
或String
.接受
&String
、&Vec
或&Box
还需要之前在堆上分配参数你可以调用这个函数。接受&str
允许字符串文字(保存在程序数据中)并接受&[T]
或&T
允许堆栈分配的数组或变量。不必要的分配是一种性能损失。当您尝试在测试或main
方法中调用这些方法时,这通常会立即暴露出来:awesome_greeting(&String::from("Anna"));
total_price(&vec![42, 13, 1337])
is_even(&Box::new(42))
另一个性能考虑因素是
&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_greeting
。 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();
}
}
除了&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]>
等