过滤 Vec<String> 时无法移出借用的内容

Cannot move out of borrowed content when filtering a Vec<String>

我正在尝试实现一个函数 return 包含模式的所有字符串的向量从 (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 循环的行(在 * 行)出现错误。我是 Rust 的新手(昨天开始学习 Rust!),现在几乎不知道如何解决这个错误。

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

简短版本: 删除取消引用并改为 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 循环使用,生成的迭代器在循环的末尾被丢弃环形。因此,为什么上面的代码失败了:我们正试图将一个 "handed over" 的变量用于其他东西。在其他语言中,使用的典型术语是循环使用的值是 consumed.

承认这种行为可以让我们找到问题的根源,即 cannot move out of borrowed content 中的 "move"。当你有一个引用时,比如 lines(对 Vec 的引用),你只有那个 - borrow。您没有该对象的 所有权 ,因此您不能 将该对象的所有权 赋予其他对象,这可能会导致 Rust 的内存错误旨在防止。取消引用 lines 有效地表示 "I want to give the original vector to this loop",这是你做不到的,因为原始向量属于其他人。

松散地说——我在这方面可能是错的,如果我错了,请有人纠正我——但在大多数情况下,Rust 中的取消引用仅对修改 left-hand 端的对象有用一个赋值,因为基本上任何在表达式的 right-hand 端使用取消引用都会尝试移动该项目。例如:

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() 是你的朋友,你应该熟悉它,但要注意它确实需要额外的内存(最多两倍,如果所有行都匹配),并且行放在 selected_lines 无法轻松链接回 lines 中的对应项。在您退出实验并使用非常大的数据集进入生产之前,您不应该担心内存问题,但是如果后一个问题造成问题,我想向您指出这个替代解决方案,它确实编辑了函数签名为了 return 引用 到匹配的行:

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

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

    selected_lines
}

此示例包括 Lifetimes,您暂时可能不需要学习这些内容,但您可以随意检查此示例!

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

问题是您正试图将 String 实例的所有权 移出 lines 参数(这是一个输入参数) ... 将所有权转移到 return 值(输出)。

有几个选项供您选择。

选项 1 - Clone

对你来说最容易理解的就是克隆这些行:

selected_lines.push(line.clone());

现在您已经克隆了这些行……没有所有权问题。您要 returning 的是向量中 String 新实例 。它们只是您传入的副本。

选项 2 - Lifetimes

另一种选择(避免额外分配)是让编译器知道您不会 return 任何悬空的引用:

// 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、向量或切片一起使用。

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

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

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

另请参阅: