【问题标题】:How do I write a function that takes both owned and non-owned string collections?如何编写一个同时接受拥有和非拥有字符串集合的函数?
【发布时间】:2015-09-22 18:02:51
【问题描述】:

我在编写一个将字符串集合作为参数的函数时遇到问题。我的函数如下所示:

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

fn my_func(list: &StrList) {
    for s in list {
        println!("{}", s);
    }
}

如果我按预期将Vec&lt;&amp;'a str&gt; 传递给函数,一切都会顺利进行。但是,如果我通过 Vec&lt;String&gt; 编译器会抱怨:

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>`

这是主要使用的:

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

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

type StrList = Vec<String>;

第一次调用失败,第二次成功。

一种可能的解决方案是以这种方式从v2 生成Vec&lt;&amp;'a str&gt;

let v2_1 : Vec<_> = v2.iter().map(|s| s.as_ref()).collect();

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

my_func 应该使用哪种签名来支持拥有的字符串向量和字符串引用?

【问题讨论】:

    标签: string rust


    【解决方案1】:

    虽然String&amp;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
    

    这里每一行都是相同的内存量(四个或八个字节取决于指针大小)。你不能记住其中一个,然后像对待另一个一样对待它。内存布局不匹配。这些物品的大小不同,布局也不同。例如,如果v1 存储其项目从地址X 开始,v2 存储其项目从地址Y 开始,则v1[1] 位于地址X + 8,但v2[1] 位于地址Y + 12 .

    可以做的就是写一个这样的通用函数:

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

    然后编译器可以为&amp;[String]&amp;[&amp;str] 以及其他类型(如果它们实现AsRef&lt;str&gt;)生成适当的代码。

    【讨论】:

    • 它确实有效。你能更好地解释为什么&amp;[T] 有效吗? AsRef&lt;str&gt; 很清楚。
    • @brt 你知道切片&amp;[T],只是想知道为什么函数接受&amp;[T]main 传入&amp;Vec&lt;T&gt;,对吗?答案是deref coercions。因为&amp;Vec&lt;T&gt; 不是&amp;[T] 的通用类型(您可以从许多不是向量的来源获得后者),所以最好编写函数来接受&amp;[T] 而不是&amp;Vec&lt;T&gt;。对于人体工程学,foo(&amp;vec) 自动从向量构造切片。
    【解决方案2】:

    为了以delnan's great answer 为基础,我想指出您可以在此处添加的更多级别的泛型。你说:

    字符串的集合

    但是集合的类型比切片和向量还多!在您的示例中,您关心对项目的一次一次仅向前访问。这是Iterator 的完美示例。下面,我更改了您的函数以接受任何可以转换为迭代器的类型。然后你可以传递更多类型的东西。我以HashSet 为例,但请注意,您也可以传入v1v2 而不是&amp;v1&amp;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);
    }
    

    【讨论】:

    • 这实际上更好! @delnan 实际上回答了我的问题,但是这个版本更通用。
    猜你喜欢
    • 2020-01-03
    • 2014-09-01
    • 1970-01-01
    • 2021-10-15
    • 1970-01-01
    • 1970-01-01
    • 2021-05-29
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多