为什么 Rust 编译器抱怨我在用新值替换移动值时使用移动值?

  • 本文关键字:移动 替换 新值 Rust 编译器 rust
  • 更新时间 :
  • 英文 :


我正在处理两个单独链接的列表,分别命名为longershorter。保证longer的长度不小于shorter的长度。

我按元素对列表进行配对,并对每一对做些什么。如果longer列表中有更多未配对的元素,则处理其余元素:

struct List {
next: Option<Box<List>>,
}
fn drain_lists(mut shorter: Option<Box<List>>, mut longer: Option<Box<List>>) {
// Pair the elements in the two lists.
while let (Some(node1), Some(node2)) = (shorter, longer) {
// Actual work elided.
shorter = node1.next;
longer = node2.next;
}
// Process the rest in the longer list.
while let Some(node) = longer {
// Actual work elided.
longer = node.next;
}
}

然而,编译器在第二个while循环中抱怨

error[E0382]: use of moved value
--> src/lib.rs:13:20
|
5  | fn drain_lists(mut shorter: Option<Box<List>>, mut longer: Option<Box<List>>) {
|                                                ---------- move occurs because `longer` has type `std::option::Option<std::boxed::Box<List>>`, which does not implement the `Copy` trait
6  |     // Pair the elements in the two lists.
7  |     while let (Some(node1), Some(node2)) = (shorter, longer) {
|                                                      ------ value moved here
...
13 |     while let Some(node) = longer {
|                    ^^^^ value used here after move

但是,我do在循环结束时为shorterlonger设置一个新值,这样我就永远不会使用它们的移动值。

我应该如何迎合编译器?

我认为问题是由第一个循环中的临时元组引起的。创建元组会将其组件移动到新的元组中,即使后续的模式匹配失败,也会发生这种情况。

首先,让我为您的代码编写一个更简单的版本。这编译得很好:

struct Foo(i32);
fn main() {
let mut longer = Foo(0);
while let Foo(x) = longer {
longer = Foo(x + 1);
}
println!("{:?}", longer.0);
}

但是,如果我在while let中添加一个临时的,那么我会触发一个类似于您的编译器错误:

fn fwd<T>(t: T) -> T { t }
struct Foo(i32);
fn main() {
let mut longer = Foo(0);
while let Foo(x) = fwd(longer) {
longer = Foo(x + 1);
}
println!("{:?}", longer.0);
//        Error: ^ borrow of moved value: `longer`
}

解决方案是添加一个具有要销毁的值的局部变量,而不是依赖于临时变量。在您的代码中:

struct List {
next: Option<Box<List>>
}
fn drain_lists(shorter: Option<Box<List>>,
longer: Option<Box<List>>) {
// Pair the elements in the two lists.
let mut twolists = (shorter, longer);
while let (Some(node1), Some(node2)) = twolists {
// Actual work elided.
twolists = (node1.next, node2.next);
}
// Process the rest in the longer list.
let (_, mut longer) = twolists;
while let Some(node) = longer {
// Actual work elided.
longer = node.next;
}
}

除了去掉元组(由其他人显示(之外,您还可以捕获对节点的可变引用:

while let (&mut Some(ref mut node1), &mut Some(ref mut node2)) = (&mut shorter, &mut longer) {
shorter = node1.next.take();
longer = node2.next.take();
}

使用take()可以实现这一点:shorter = node1.next会抱怨将字段从引用中移出,这是不允许的(这会使node处于未定义状态(。但是take可以,因为它在next字段中留下None

看起来第7行的析构函数会移动值,即使之后没有计算块。(编辑:正如@Sven Marnach在评论中指出的那样,这里会创建一个临时元组,导致移动(为了证明这一点,我对你的代码进行了篡改:(

struct List {
next: Option<Box<List>>
}
fn drain_lists(mut shorter: Option<Box<List>>,
mut longer: Option<Box<List>>) {
// Pair the elements in the two lists.
match(shorter, longer) {
(Some(node1), Some(node2)) => {
shorter = node1.next;
longer = node2.next;
},
(_, _) => return  // without this you get the error
}
// Process the rest in the longer list.
while let Some(node) = longer {
// Actual work elided.
longer = node.next;
}
} 

当我为默认情况添加返回时,代码已编译。

一种解决方案是避免元组,从而避免将较长的元组移动到元组中。

fn actual_work(node1: &Box<List>, node2: &Box<List>) {
// Actual work elided
}
fn drain_lists(mut shorter: Option<Box<List>>, mut longer: Option<Box<List>>) {
while let Some(node1) = shorter {
if let Some(node2) = longer.as_ref() {
actual_work(&node1, node2);
}
shorter = node1.next;
longer = longer.map_or(None, move |l| {
l.next
});
}
// Process the rest in the longer list.
while let Some(node) = longer {
// Actual work elided.
longer = node.next;
}
}

相关内容

  • 没有找到相关文章