4

I have a struct which owns a boxed value of some trait type. The struct itself also implements the same trait. I would like to replace the value with a new instance of the same struct, which wraps it.

The following code, which does not compile, should make it more clear what I am trying to do:

trait T {}

struct S {
    t: Box<dyn T>,
}
impl T for S {}

impl S {
    fn new(t: Box<dyn T>) -> Self {
        Self { t }
    }

    fn wrap_t(&mut self) {
        self.t = Box::new(Self::new(self.t))
    }
}

This fails:

error[E0507]: cannot move out of borrowed content
  --> src/lib.rs:14:37
   |
14 |         self.t = Box::new(Self::new(self.t))
   |                                     ^^^^ cannot move out of borrowed content

Implementing wrap_t like this does compile:

use std::mem;

fn wrap_t(&mut self) {
    unsafe {
        let old_t = mem::replace(&mut self.t, mem::uninitialized());
        let new_t = Box::new(Self::new(old_t));
        let uninit = mem::replace(&mut self.t, new_t);
        mem::forget(uninit);
    }
}

I wonder if there is a safe way to do this.

Peter Hall
  • 53,120
  • 14
  • 139
  • 204
Brennan Vincent
  • 10,736
  • 9
  • 32
  • 54
  • 1
    What behavior do you think should happen if the code panics once the value has been moved out once but before it is moved back? – Shepmaster Sep 23 '18 at 02:17
  • See also [Change enum variant while moving the field to the new variant](https://stackoverflow.com/q/36557412/155423); [Temporarily move out of borrowed content](https://stackoverflow.com/q/29570781/155423). – Shepmaster Sep 23 '18 at 02:20
  • @Shepmaster I don’t have a good answer. In my real program, I am only calling code that is guaranteed not to panic. – Brennan Vincent Sep 23 '18 at 02:28

1 Answers1

1

The only unsafe function you are using is mem::uninitialized. You need something to pass to mem::replace, but implementing Default won't work because default() returns Self, which prevents it from being object-safe. Similarly, you can't implement Clone to duplicate the old value, since clone() also returns Self.

You can just implement a dummy type for the purpose though:

struct Dummy;
impl T for Dummy {}

fn wrap_t(&mut self) {
    let old_t = mem::replace(&mut self.t, Box::new(Dummy));
    let new_t = Box::new(Self::new(old_t));
    mem::replace(&mut self.t, new_t);
}

You also won't need the mem::forget here now either (I'm assuming that was there to prevent undefined behaviour when the uninitialised memory was dropped).


As an alternative to Clone, you can roll your own own, which clones to a Box<dyn T>, avoiding having a Self in the method signature, so the trait stays object safe:

trait T: Debug {
    fn clone_in_box(&self) -> Box<dyn T>;
}

impl T for S {
    fn clone_in_box(&self) -> Box<dyn T> {
        Box::new(S {
            t: self.t.clone_in_box(),
        })
    }
}
fn wrap_t(&mut self) {
    let cloned = self.clone_in_box();
    let old_t = mem::replace(&mut self.t, cloned);
    let new_t = Box::new(Self::new(old_t));
    mem::replace(&mut self.t, new_t);
}

There is also an alternative design, which is much simpler to understand when reading the code. That is just to consume self and return a new object:

fn wrap_t(self) -> Self {
    Self::new(Box::new(Self::new(self.t)))
}

And instead of this:

s.wrap_t();

You would do:

s = s.wrap_t();
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Peter Hall
  • 53,120
  • 14
  • 139
  • 204
  • The first solution will work, but it is a bit annoying since `T` might have a lot of methods. I guess for `Dummy` I would have to implement all those methods with stub implementations. The second solution won't work for me, as `wrap_t` might be called from another method of `S`, so it does need to take `self` by reference. – Brennan Vincent Sep 23 '18 at 01:40