Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I reuse a box that I have moved the value out of?

I have some non-copyable type and a function that consumes and (maybe) produces it:

type Foo = Vec<u8>;

fn quux(_: Foo) -> Option<Foo> {
    Some(Vec::new())
}

Now consider a type that is somehow conceptually very similar to Box:

struct NotBox<T> {
    contents: T
}

We can write a function that temporarily moves out contents of the NotBox and puts something back in before returning it:

fn bar(mut notbox: NotBox<Foo>) -> Option<NotBox<Foo>> {
    let foo = notbox.contents; // now `notbox` is "empty"
    match quux(foo) {
        Some(new_foo) => {
            notbox.contents = new_foo; // we put something back in
            Some(notbox)
        }
        None => None
    }
}

I want to write an analogous function that works with Boxes but the compiler does not like it:

fn baz(mut abox: Box<Foo>) -> Option<Box<Foo>> {
    let foo = *abox; // now `abox` is "empty"
    match quux(foo) {
        Some(new_foo) => {
            *abox = new_foo; // error: use of moved value: `abox`
            Some(abox)
        }
        None => None
    }
}

I could return Some(Box::new(new_foo)) instead but that performs unnecessary allocation - I already have some memory at my disposal! Is it possible to avoid that?

I would also like to get rid of the match statements but again the compiler is not happy with it (even for the NotBox version):

fn bar(mut notbox: NotBox<Foo>) -> Option<NotBox<Foo>> {
    let foo = notbox.contents;
    quux(foo).map(|new_foo| {
        notbox.contents = new_foo; // error: capture of partially moved value: `notbox`
        notbox
    })
}

Is it possible to work around that?

like image 353
Pan Hania Avatar asked Jul 15 '16 13:07

Pan Hania


2 Answers

So, moving out of a Box is a special case... now what?

The std::mem module presents a number of safe functions to move values around, without poking holes (!) into the memory safety of Rust. Of interest here are swap and replace:

pub fn replace<T>(dest: &mut T, src: T) -> T

Which we can use like so:

fn baz(mut abox: Box<Foo>) -> Option<Box<Foo>> {
    let foo = std::mem::replace(&mut *abox, Foo::default());

    match quux(foo) {
        Some(new_foo) => {
            *abox = new_foo;
            Some(abox)
        }
        None => None
    }
}

It also helps in the map case, because it does not borrow the Box:

fn baz(mut abox: Box<Foo>) -> Option<Box<Foo>> {
    let foo = std::mem::replace(&mut *abox, Foo::default());

    quux(foo).map(|new_foo| { *abox = new_foo; abox })
}
like image 68
Matthieu M. Avatar answered Sep 21 '22 05:09

Matthieu M.


Moving out of boxes is special-cased in the compiler. You can move something out of them, but you can't move something back in, because the act of moving out also deallocates. You can do something silly with std::ptr::write, std::ptr::read, and std::ptr::replace, but it's hard to get it right, because something valid should be inside a Box when it is dropped. I would recommend just accepting the allocation, or switching to a Box<Option<Foo>> instead.

like image 41
Thiez Avatar answered Sep 25 '22 05:09

Thiez