过滤 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);
}
当运行此代码时,会引发以下错误:
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);
}
以上代码将打印:
[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
}
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);
}
其他答案是正确的,但惯用的解决方案不会涉及重新发明轮子:
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);
}
另请参阅:
我正在尝试实现一个函数 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);
}
当运行此代码时,会引发以下错误:
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);
}
以上代码将打印:
[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
}
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);
}
其他答案是正确的,但惯用的解决方案不会涉及重新发明轮子:
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);
}
另请参阅: