我正在处理两个单独链接的列表,分别命名为longer
和shorter
。保证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在循环结束时为shorter
和longer
设置一个新值,这样我就永远不会使用它们的移动值。
我应该如何迎合编译器?
我认为问题是由第一个循环中的临时元组引起的。创建元组会将其组件移动到新的元组中,即使后续的模式匹配失败,也会发生这种情况。
首先,让我为您的代码编写一个更简单的版本。这编译得很好:
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;
}
}