简体   繁体   中英

Why does the Rust compiler complain that I use a moved value when I've replaced it with a new value?

I am working on two singly linked lists, named longer and shorter . The length of the longer one is guaranteed to be no less than the shorter one.

I pair the lists element-wise and do something to each pair. If the longer list has more unpaired elements, process the rest of them:

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

However, the compiler complains on the second while loop that

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

However, I do set a new value for shorter and longer at the end of the loop, so that I will never use a moved value of them.

How should I cater to the compiler?

I think that the problem is caused by the tuple temporary in the first loop. Creating a tuple moves its components into the new tuple, and that happens even when the subsequent pattern matching fails.

First, let me write a simpler version of your code. This compiles fine:

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

But if I add a temporary to the while let then I'll trigger a compiler error similar to yours:

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`
}

The solution is to add a local variable with the value to be destructured, instead of relying on a temporary. In your code:

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

Other than getting rid of the tuple (shown by others), you can capture a mutable reference to the nodes:

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

The use of take() enables this to work: shorter = node1.next would complain of moving a field out of a reference, which is not allowed (it would leave the node in an undefined state). But take ing it is ok because it leaves None in the next field.

Looks like the destructuring on line 7 moves the value even when the block afterwards is not evaluated. (Edit: as @Sven Marnach pointed out in the comments, a temporary tuple gets created here which causes the move) I've uglyfied your code to prove that point :)

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

When I added the return for the default case, the code compiled.

One solution is to avoid the tuple and consequently the move of longer into the tuple.

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

}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM