Use of collaterally moved value error on a recursi

2019-07-20 03:10发布

问题:

I have a recursive Item structure that I am using to implement lists:

#[derive(Debug)]
pub enum Item<T> {
    Cons(T, Box<Item<T>>),
    Nil,
}

When implementing a function that inserts an element after another one, I found out that the Rust compiler wasn't that happy about my code:

pub fn add_after<T>(it: Box<Item<T>>, val: T) -> Box<Item<T>> {
    match *it {
        Item::Nil => return it,
        Item::Cons(a, b) => {
            let itm = Box::new(Item::Cons(val, b));
            return Box::new(Item::Cons(a, itm));
        }
    }
}

The errors that I get are pretty obscure for a newbie:

error[E0382]: use of collaterally moved value: `(it as Item::Cons).1`
  --> src/main.rs:12:23
   |
12 |         Item::Cons(a, b) => {
   |                    -  ^ value used here after move
   |                    |
   |                    value moved here
   |
   = note: move occurs because the value has type `T`, which does not implement the `Copy` trait

Another similar question suggested to do the unwrapping phase in two steps but it cannot be used here because we need to directly unwrap a two-fields Cons(..) item and not nested items like Option<Box<Whatever>> where the two-phase trick can be applied. Example of what I tried:

pub fn add_after<T>(it: Box<Item<T>>, val: T) -> Box<Item<T>> {
    match *it {
        Item::Nil => return it,
        Item::Cons(..) => {
            let Item::Cons(a, b) = *it;
            let itm = Box::new(Item::Cons(val, b));
            return Box::new(Item::Cons(a, itm));
        }
    }
}

But I get another error:

error[E0005]: refutable pattern in local binding: `Nil` not covered
  --> src/main.rs:13:17
   |
13 |             let Item::Cons(a, b) = *it;
   |                 ^^^^^^^^^^^^^^^^ pattern `Nil` not covered

Though I am pretty sure here that this is exhaustive at this point because we matched a Cons before.

回答1:

You may be suffering from issue 16223 (see also 22205 which has a closer reproduction), although today's non-lexical lifetimes don't solve this problem. This seems to preclude destructuring multiple things through a Box.

Here's one way to work around it, although it's not the most efficient way as it deallocates and reallocates unnecessarily:

#[derive(Debug)]
pub enum Item<T> {
    Cons(T, Box<Item<T>>),
    Nil,
}

pub fn add_after<T>(it: Box<Item<T>>, val: T) -> Box<Item<T>> {
    match { *it } {
        Item::Nil => Box::new(Item::Nil),
        Item::Cons(a, b) => {
            let itm = Box::new(Item::Cons(val, b));
            Box::new(Item::Cons(a, itm))
        }
    }
}

fn main() {}

A more verbose way pulls the value out of the Box, manipulates that, and then puts the manipulated value back into the Box. This should have a reduced amount of allocations:

use std::mem;

pub fn add_after<T>(mut item: Box<Item<T>>, val: T) -> Box<Item<T>> {
    let unboxed_value = mem::replace(&mut *item, Item::Nil);

    match unboxed_value {
        Item::Nil => item,
        Item::Cons(a, b) => {
            let itm = Box::new(Item::Cons(val, b));
            *item = Item::Cons(a, itm);
            item
        }
    }
}

See also:

  • Collaterally moved error when deconstructing a Box of pairs