I got a lot of troubles trying to find a solution of my problem.
My goal is to push a Box<dyn Trait> into a Vec<Box<dyn Trait>>, and overwrite the first reference, while both my variable are in a self context.
Here's the Rust Playground for more informations and context, it's a remake of my problem.
And the code as an image even tho the rust playground link add more informations to it.
I'm also develloping this in a #[no_std] environnement but it shouldn't matter since I made the allocator.
Should I use an unsafe ? I hope for a solution without.
use std::mem;
let old = mem::replace(&mut self.variable, Box::new(Test::Three));
self.vector.push(old);
Note that there's a very good and specific reason why you are not allowed to "just" move out of the value pointed to by self.variable. If you move out of that variable, there will be no valid, initialized value behind the reference. That is not allowed; every reference must have a valid pointee at all times. Basically anything else is instant undefined behavior (if you manage to circumvent compiler checks using unsafe).
Unless you already have a thorough understanding of the memory model of safe Rust, you should absolutely not use unsafe yet, especially for such trivial tasks which the standard library is well-suited for.
The most prominent reason why Rust doesn’t allow something like
self.vector.push(self.variable);
// right here, `self.variable` is kind-of “uninitialized”
// i.e. does not have/own a value
self.variable = Box::new(Test::Three);
is panic-safety. The value self.variable would need to become logically “uninitialized” (in practice it’d realistically still be containing a copy of the Box<dyn Trait>) between the two lines of code; and this is a huge problem if the code panics after self.variable was consumed and before it’s newly assigned. On panic, during unwinding, you cannot just leave the borrowed &mut Program in a state where variable is uninitialized; if you used unsafe code to do the same, thing, a panic would most likely result in a double-free of the Box<dyn Trait> because, as mentioned above, a copy of the old value is still present in self.variable.
I’d also say that this problem can be solved i.e. it doesn’t have to be this way. Rust could (and perhaps even will at some point in the distant future) support leaving value behind &mut-references uninitialized as long as the code cannot panic in the mean-time. You’d probably need a way to annotate whether functions can panic, and you could have some form of panic_becomes_abort { … } blocks that turns panics inside to aborts outside (aborts don’t do unwinding, so they aren’t a problem for panic-safety).
For this concrete code example, IIRC, Box::new can actually panic when out of memory, and Vec::push can do the same thing. Both operations happen afterself.variable is consumed and before the new value is assigner, so
is actually problematic; the code from @H2CO3 that uses mem::replace will instead first allocate the box, then go through the consuming and re-assigning self.variable and finally do the Vec::push, which is actually the only order you can do the operations in in order to remain panic-safe. Using the safe API from the standard library nicely forces you to do things correctly and soundly here.
Indeed you are right. I was lying in order to provide a simplified explanation (and hopefully slightly discourage OP from reaching to unsafe if there are std-supplied solution).