2

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.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
MicroJoe
  • 267
  • 3
  • 12

1 Answers1

2

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:

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • The Item::Nil case may not be matched alot since this value will just do nothing to the list and so may not be passed to the function. However we still have to create two new items instead of modifying the first one in order to avoid some alloc/free. – MicroJoe Feb 20 '15 at 22:25