过滤Vec <string>时无法移出借来的内容

时间:2018-04-10 04:11:10

标签: pointers types rust dereference lifetime

我正在尝试实现一个函数,以返回包含(Vec<String>)模式的所有字符串的向量,并返回到另一个Vec<String>

这就是我的尝试:

fn select_lines(pattern: &String, lines: &Vec<String>) -> Vec<String> {
    let mut selected_lines: Vec<String> = Vec::new();

    for line in *lines {
        if line.contains(pattern) {
            selected_lines.push(line);
        }
    }

    selected_lines
}

使用for循环(at * lines)导致行错误。我是Rust的新手(昨天开始学习Rust!),现在几乎无法解决这个错误。

我可以移除*并且该错误消失但有关类型不匹配的错误开始达到高潮。我想保持功能的签名完好无损。有办法吗?

3 个答案:

答案 0 :(得分:5)

问题在于您是否试图移动String参数的lines实例 的所有权(这是一个输入参数)...将所有权转移到返回值(输出)。

有几种选择。

选项1 - Clone

最简单的方法就是克隆这些线:

selected_lines.push(line.clone());

既然你已克隆了这些界限......那就没有所有权问题了。您正在返回的内容是向量中的String新实例。它们只是你传入的副本。

选项2 - Lifetimes

另一个选择(避免额外的分配)是让编译器知道你不会返回任何悬挂的引用:

// introduce a lifetime to let the compiler know what you're
// trying to do. This lifetime basically says "the Strings I'm returning
// in the vector live for at least as long as the Strings coming in
fn select_lines<'a>(pattern: &String, lines: &'a Vec<String>) -> Vec<&'a String> { 
    let mut selected_lines: Vec<&String> = Vec::new();

    for line in lines {
        if line.contains(pattern) {
            selected_lines.push(line);
        }
    }

    selected_lines
}

这就是你如何解决眼前问题的方法。

另一个旋转

如果我写这个,我会略微改变它。这是另一个旋转:

fn select_lines<I>(pattern: I, lines: &[I]) -> Vec<&str>
where
    I: AsRef<str>,
{
    let mut selected_lines: Vec<&str> = Vec::new();

    for line in lines {
        if line.as_ref().contains(pattern.as_ref()) {
            selected_lines.push(line.as_ref());
        }
    }

    selected_lines
}

您可以将此版本与String&str s,向量或切片一起使用。

let lines = vec!["Hello", "Stack", "overflow"];

let selected = select_lines("over", &lines);

// prints "overflow"
for l in selected {
    println!("Line: {}", l);
}

let lines2 = [String::from("Hello"), String::from("Stack"), "overflow".into()];

let selected2 = select_lines(String::from("He"), &lines2);

// prints "Hello"
for l in selected2 {
    println!("Line again: {}", l);
}

Here it is running on the playground

答案 1 :(得分:1)

其他答案是正确的,但惯用的解决方案不会涉及重新发明轮子:

fn main() {
    let lines = vec!["Hello", "Stack", "overflow"];

    // Vec<String>
    let selected: Vec<_> = lines
        .iter()
        .filter(|l| l.contains("over"))
        .cloned()
        .collect();
    println!("{:?}", selected);

    // Vec<&String>
    let selected: Vec<_> = lines
        .iter()
        .filter(|l| l.contains("over"))
        .collect();
    println!("{:?}", selected);
}

另见:

答案 2 :(得分:0)

简短版本:取消取消引用并改为push(line.clone())

原因

请使用以下代码:

fn main() {
    let mut foo = vec![1, 2, 3, 4, 5];

    for num in foo {
        println!("{}", num);
    }

    foo.push(6);
}

Playground

运行此代码时,会引发以下错误:

error[E0382]: use of moved value: `foo`
 --> src/main.rs:8:5
  |
4 |     for num in foo {
  |                --- value moved here
...
8 |     foo.push(6);
  |     ^^^ value used here after move
  |
  = note: move occurs because `foo` has type `std::vec::Vec<i32>`, which does not implement the `Copy` trait

此错误上升是因为Rust for循环获取有问题的迭代器的所有权,特别是通过IntoIterator特征。上述代码中的for循环可以等效地写为for num in foo.into_iter()

请注意signature of into_iter()。它需要self而不是&self;换句话说,值的所有权被移动到函数中,这将创建一个在for循环中使用的迭代器,并且在循环结束时删除生成的迭代器。因此,为什么上面的代码失败了:我们试图使用一个被移交的变量&#34;别的东西。在其他语言中,使用的典型术语是用于循环的值消耗

承认这种行为会让我们找到问题的根源,即&#34;移动&#34;在cannot move out of borrowed content。当您有一个引用时,例如lines(对Vec的引用),您只有 - 借用。您没有该对象的所有权,因此您无法将该对象的所有权提供给其他内容,这可能会导致Rust旨在阻止的内存错误。解除引用lines有效地说&#34;我想将原始载体提供给此循环&#34;,您无法做到,因为原始载体属于其他人

松散地说 - 我可能在这方面做错了,如果我是的话请有人纠正我 - 但在大多数情况下,在Rust中取消引用只对修改作业左侧的对象有用,因为基本上任何在表达式右侧使用取消引用都会尝试移动该项。例如:

fn main() {
    let mut foo = vec![1, 2, 3, 4, 5];
    println!("{:?}", foo);

    {
        let num_ref: &mut i32 = &mut foo[2]; // Take a reference to an item in the vec
        *num_ref = 12; // Modify the item pointed to by num_ref
    }

    println!("{:?}", foo);
}

Playground

上面的代码将打印出来:

[1, 2, 3, 4, 5]
[1, 2, 12, 4, 5]

如何

所以不幸的是,在这种情况下无法使用解除引用。但是你很幸运 - 这是解决你的类型不匹配问题的简单方法。方便特性Clone定义了一个名为clone()的函数,该函数需要创建一个具有相同值的全新类型实例。 Rust实现Clone中的大多数基本类型,包括String。因此,通过单个函数调用,您的类型不匹配问题就会消失:

fn select_lines(pattern: &String, lines: &Vec<String>) -> Vec<String> {
    let mut selected_lines: Vec<String> = Vec::new();

    for line in lines {
        if line.contains(pattern) {
            // 'line.clone()' will create a new String for
            // each line that contains the pattern, and
            // place it into the vector.
            selected_lines.push(line.clone());
        }
    }

    selected_lines
}

Playground, with an example

clone()是你的朋友,你应该熟悉它,但要注意它确实需要额外的内存(如果所有行都匹配,最多可以加倍),并且行放入{{1 }}无法轻易链接回selected_lines中的对应者。在你离开实验并使用非常大的数据集进入生产之前,你不应该担心内存问题,但如果后一个问题出现问题,我想指出这个替代解决方案,编辑函数签名,以便将引用返回到匹配的行:

lines

此示例包含Lifetimes,这是您可能不会需要学习一段时间的内容,但您可以随意查看此示例!

Playground link, with an example, and some mutability edits.