Shadowing box contents into itself in &mut self method

I've got a struct that holds a box of a dyn InnerTrait type:

struct HoldsBox {
    boxed: Box<dyn InnerTrait>,
}

I'm running into an issue of not being able to move the box contents, even though I'm just trying to shadow the value into itself

impl HoldsBox {
    fn change_box(&mut self, option: SomeEnum) {
        self.boxed = self.boxed.apply_option(option);
        //           ^^^^^^^^^^ move error here, because of &mut ref of self
    }
}

The problem occurs because the interior trait method takes the boxed self by value:

impl InnerTrait for InnerStruct {
    fn apply_option(self: Box<Self>, option: SomeEnum) -> Box<dyn InnerTrait> {
        // consume self and use to create a new boxed version
    }
}

So it doesn't work even though the value is taken and immediately replaced back into itself.

I'm guessing the answer might involve some sync primitives, including at least a Mutex, but my first attempts with it just moved the error into the Mutex write operation. Can anyone point me to what I'm missing?

No, this is not related to synchronization. You can't move out of a reference because then it wouldn't point to anything, and that is a violation of the memory model of the language. So what you want isn't possible as-is.

You should probably just redesign the InnerTrait so that apply_option takes &mut self. Alternatively (but this is not elegant), if it is possible to come up with a dummy value of type Box<dyn InnerTrait>, you could use mem::replace().

3 Likes

The inner struct that the trait works on uses a typestate / type-setting inner function, but the inner trait needs to remain object safe:

impl InnerStruct<T> {
    // This is what gets called inside the method which takes Box<Self>
    fn change_type<T2>(self, new_type: T2) -> InnerStruct<T2> {
        // replace type T with newly supplied T2
    }
}

However, I am setting this up around a builder pattern, and the first setup is an entirely ZST struct, so mem::replace() will probably do the trick. Much appreciated!

1 Like

In the worst case, you can write a zero-sized struct that calls unimplemented!() inside every trait method.

3 Likes

you can modify the HoldsBox to hold an Option<Box<dyn InnerStruct>>, and then .take().unwrap() to take out the value, then wrap the whole expression in Some(...)

2 Likes

That was actually a direction I looked at, but didn't manage to connect things enough to get it working.

Well, now I've got two working options:

  1. Using mem::replace() - playground
  2. Using Option/take() - playground

The second option had just one minor adjustment required, in that I had to add an .as_mut() before unwrapping to call an &mut self method on the option contents, but otherwise both forms work the same and are probably approximately equivalent. I guess the Option version would be just a hair more efficient as there at no time needs to be two boxes (even though one holds a ZST), and Option<Box<_>> is null pointer optimized...

1 Like

well i had not considered the optimization aspects when writing but that's a plus i guess.

Box<ZST> doesn't allocate. Instead it always returns the smallest non-zero pointer with the correct alignment. As a result, in theory, mem::replace with a zero sized type should be marginally more efficient as it avoids the branch to check for null when calling unwrap but is otherwise equivalent. In practice though, I'd expect the difference between the two to be negligible/nonexistent.

4 Likes

This has been incredibly interesting. I really wish I could mark two responses as solutions... But, I guess it goes to mem::replace() by the tiniest of margins! Thanks everyone!!

You may also be interested in this blog post: Moving from borrowed data and the sentinel pattern.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.