如何编写一个既拥有和不拥有字符串集合的函数?

mbr*_*brt 9 string rust

我在编写一个将字符串集合作为参数的函数时遇到了麻烦.我的功能看起来像这样:

type StrList<'a> = Vec<&'a str>;

fn my_func(list: &StrList) {
    for s in list {
        println!("{}", s);
    }
}
Run Code Online (Sandbox Code Playgroud)

如果我Vec<&'a str>按预期传递给函数,一切顺利.但是,如果我通过Vec<String>编译器抱怨:

error[E0308]: mismatched types
  --> src/main.rs:13:13
   |
13 |     my_func(&v2);
   |             ^^^ expected &str, found struct `std::string::String`
   |
   = note: expected type `&std::vec::Vec<&str>`
   = note:    found type `&std::vec::Vec<std::string::String>`
Run Code Online (Sandbox Code Playgroud)

这是主要使用的:

fn main() {
    let v1 = vec!["a", "b"];
    let v2 = vec!["a".to_owned(), "b".to_owned()];
    my_func(&v1);
    my_func(&v2);
}
Run Code Online (Sandbox Code Playgroud)

我的函数无法获取自有字符串的向量.相反,如果我将StrList类型更改为:

type StrList = Vec<String>;
Run Code Online (Sandbox Code Playgroud)

第一次调用失败,第二次调用失败.

一种可能的解决方案是以这种方式生成一个Vec<&'a str>from v2:

let v2_1 : Vec<_> = v2.iter().map(|s| s.as_ref()).collect();
Run Code Online (Sandbox Code Playgroud)

但对我来说这似乎很奇怪.my_func不应该关心字符串的所有权.

我应该使用什么样的签名my_func来支持自有字符串和字符串引用的向量?

小智 18

虽然String并且&str密切相关,但它们并不完全相同.这是你的向量在内存中的样子:

v1 ---> [ { 0x7890, // pointer to "a" + 7 unused bytes
            1 }     // length of "a"
          { 0x7898, // pointer to "b" + 7 unused bytes
            1 } ]   // length

v2 ---> [ { 0x1230 // pointer to "a" + 7 unused bytes (a different copy)
            8      // capacity
            1 }    // length
          { 0x1238 // pointer ...
            8      // capacity
            1 } ]  // length
Run Code Online (Sandbox Code Playgroud)

这里每行的内存量相同(四个或八个字节,具体取决于指针大小).你不能把它们中的一个记忆下来并像对方一样对待它.内存布局不匹配.这些物品的大小不同,布局也不同.例如,如果v1存储其项目从地址开始Xv2存储其从地址开始的项目Y,则v1[1]位于地址X + 8v2[1]位于地址Y + 12.

可以做的是编写这样一个泛型函数:

fn my_func<T: AsRef<str>>(list: &[T]) {
    for s in list {
        println!("{}", s.as_ref());
    }
}
Run Code Online (Sandbox Code Playgroud)

那么编译器可以产生两个相应的代码&[String]&[&str]他们是否落实,以及其他类型AsRef<str>.

  • @brt你知道切片`&[T]`并且只想知道为什么函数接受`&[T]`但``main`传递给'&Vec <T>`,对吧?答案是[deref强制](http://doc.rust-lang.org/book/deref-coercions.html).因为`&Vec <T>`是一个比`&[T]`更不普遍的类型(你可以从很多不是向量的来源得到后者),所以最好编写函数来接受`&[T]`而不是比`&Vec <T>`.对于人体工程学,`foo(&vec)`自动从矢量构造切片. (2认同)

She*_*ter 5

为了以delnan 的出色答案为基础,我想指出您可以在此处添加的又一层泛型。你说:

字符串的集合

但集合的类型比切片和向量更多!在您的示例中,您关心的是对项目的仅前向、一次一次的访问。这是一个完美的例子Iterator。下面,我更改了您的函数以接受可以转换为迭代器的任何类型。然后你可以传递更多类型的东西。我使用 aHashSet作为示例,但请注意,您也可以传入v1andv2而不是&v1or&v2来使用它们。

use std::collections::HashSet;

fn my_func<I>(list: I)
    where I: IntoIterator,
          I::Item: AsRef<str>,
{
    for s in list {
        println!("{}", s.as_ref());
    }
}

fn main() {
    let v1 = vec!["a", "b"];
    let v2 = vec!["a".to_owned(), "b".to_owned()];
    let v3 = {
        let mut set = HashSet::new();
        set.insert("a");
        set.insert("b");
        set.insert("a");
        set
    };
    let v4 = {
        let mut set = HashSet::new();
        set.insert("a".to_owned());
        set.insert("b".to_owned());
        set.insert("a".to_owned());
        set
    };

    my_func(&v1);
    my_func(v1);
    my_func(&v2);
    my_func(v2);
    my_func(&v3);
    my_func(v3);
    my_func(&v4);
    my_func(v4);
}
Run Code Online (Sandbox Code Playgroud)