Brennan Vincent
Brennan Vincent

Reputation: 10675

Replace a struct member with a new value that uses the previous value

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.

Upvotes: 4

Views: 1140

Answers (1)

Peter Hall
Peter Hall

Reputation: 58875

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();

Upvotes: 1

Related Questions