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

lnvxswe2  于 2023-02-19  发布在  其他
关注(0)|答案(4)|浏览(170)

我写了一些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
}

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

fnvucqvd

fnvucqvd1#

    • TL; DR:您可以使用&str&[T]&T来支持更通用的代码。**

1.使用StringVec的一个主要原因是它们允许增加或减少容量,但是,当你接受一个不可变的引用时,你就不能在VecString上使用这些有趣的方法。
1.接受&String&Vec&Box要求在调用函数之前在堆上分配参数。接受&str允许字符串文字(保存在程序数据中)并接受&[T]&T允许堆栈分配数组或变量。不必要的分配会导致性能损失。当您尝试在测试或main方法中调用这些方法时,通常会立即暴露这些方法:

awesome_greeting(&String::from("Anna"));
total_price(&vec![42, 13, 1337])
is_even(&Box::new(42))

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>):

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();
    }
}
t30tvxxf

t30tvxxf2#

除了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]>等。

tf7tbtn2

tf7tbtn23#

建议使用&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字符串的存储效率。如果你使用向量,你必须重复所有的艰苦工作。除此之外,借用向量或装箱的值应该是可以的。

v1uwarro

v1uwarro4#

因为这些类型可以被强制,所以如果我们使用这些类型,函数将接受更少的类型:
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作为类型传递,则可以同时传递两种类型
3-对Vec的引用和对数组的引用之间存在类似的关系

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]){..}

相关问题