5

If I do the following, I get an error:

struct A;
struct B;

fn consume_a(_a: A) {}
fn consume_b(_b: B) {}

struct C(A, B);

impl C {
    fn foo(self: Self) {
        consume_a(self.0);
        consume_b(self.1);
    }
}

fn main() {
    let c = Box::new(C(A, B));

    // Consume internals
    let _a = c.0;
    let _b = c.1;
}
error[E0382]: use of moved value: `c`
  --> src/main.rs:21:9
   |
20 |     let _a = c.0;
   |         -- value moved here
21 |     let _b = c.1;
   |         ^^ value used here after move
   |
   = note: move occurs because `c.0` has type `A`, which does not implement the `Copy` trait

I can achieve the same thing (consumption of internals) doing this:

fn main() {
    let c = Box::new(C(A, B));
    c.foo();
}

The way it works above (c.foo()) means that I have moved out of boxed content; how can this happen? None of the API's in Box's documentation show I can obtain the contained value as a type (i.e. all methods return &T or &mut T but not T)

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
ustulation
  • 3,600
  • 25
  • 50

2 Answers2

6

As you can see in the method, moving out of a struct's field directly works fine, but moving out of a field of a struct that's in a Box will first move out of the Box into a temporary variable and then move out of the field of that temporary. Thus when you try to move out of the second field, the Box has already been destroyed and there's just a temporary left that you can't use.

You can make this work by creating the temporary yourself:

let c2 = *c;
let _a = c2.0;
let _b = c2.1;
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
oli_obk
  • 28,729
  • 6
  • 82
  • 98
  • But `*c` should yeild either `&C` or `&mut C` no ? Then how is `let _a = c2.0;` even allowed ? That is also one of my questions. – ustulation Jul 07 '16 at 10:37
  • 2
    `Box` is special. If you dereference a `Box` you get the inner value. There's no way yet to implement this for your own types, but there's an RFC for it: https://github.com/rust-lang/rfcs/pull/1646 – oli_obk Jul 07 '16 at 10:45
  • ah ok. Sorry for being further confused, but if `let c2 = *c;` moves out of `Box` to create a temporary as you said, how does it work if it were a `Box` of a `trait` - i mean how does compiler know the size to allocate on stack for that temporary ? – ustulation Jul 07 '16 at 10:59
  • That obviously won't work, but it's not necessary to work, since you can't move out of a trait object in general. – oli_obk Jul 07 '16 at 11:00
  • right - so you mean it's something like compiler keeping a track of whether it's a `Box` of a trait object or a concrete object somewhere and using that metadata to determine what should the behavior/result of `*c` be ? (btw is this documented somewhere - it would spare you the trouble of explaining :-) – ustulation Jul 07 '16 at 11:05
  • Actually the Compiler doesn't really track anything, it knows the type, and if you try to deref a `Box` but `T` doesn't implement [`Sized`](https://doc.rust-lang.org/std/marker/trait.Sized.html), then the deref can only be one that creates a `&T` or `&mut T`. If I had found the docs to this I'd have posted it in the answer, but sadly I can't find anything anywhere. This probably stems from the fact that `Box` didn't always exist in Rust, but it was a builtin pointer type with the `~` sigil – oli_obk Jul 07 '16 at 11:14
  • ah right - nw - your explanations did clear up many doubts i had. So next time i see something a little unexpected with `Box` i'll probably think it's a special handling by the compiler. – ustulation Jul 07 '16 at 11:20
2

Your original code works when non-lexical lifetimes are enabled:

#![feature(nll)]

struct A;
struct B;

fn consume_a(_a: A) {}
fn consume_b(_b: B) {}

struct C(A, B);

impl C {
    fn foo(self: Self) {
        consume_a(self.0);
        consume_b(self.1);
    }
}

fn main() {
    let c = Box::new(C(A, B));

    // Consume internals
    let _a = c.0;
    let _b = c.1;
}

This indicates that the original failure was simply a weakness of the borrow-checker.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366