Replacing a field in a borrowed struct


#1

I have a struct that maintains some general state with an enum:

struct Builder {
    state: BuilderState,
}

enum BuilderState {
    Pending { /* a bunch of configuration fields */ },
    Building,
}

I want to write a method that takes the contents of the state field and constructs a new value for it based on the old contents. Something like this:

impl Builder {
    fn do_stuff(&mut self) {
        self.state = match self.state {
            BuilderState::Pending { /* non-ref bindings */ } => {
                /* do stuff with the taken values */
                BuilderState::Building
            }
            state => state,
        }
    }
}

That’s unfortunately not possible, but is there some established pattern how to deal with such situations? I’d rather avoid having some invalid intermediate state for the object if that’s possible.


#2

There’s the take_mut crate. It’ll abort if the replacement value calculation panics, however.

Otherwise, there’re ways to build state machines using type state (aka session types), which sidesteps this whole issue at the expense of more code and some complexity increase.

You can also store the state in an Option and then take() the value out to compute the new one from it. This is a bit of what you’re trying to avoid, though, although you don’t need to come up with your own “invalid” variant.


#3

Thanks for the reference to take_mut, I didn’t know about it. I left out the part where I need do_stuff to return a value that gets calculated after taking state, so unfortunately I can’t use it as it is.
I guess I’ll use Option for now, since session types are not something I’d like to expose to clients of this struct.


#4

Maybe I’m misunderstanding your case but you own the state value inside the closure in take().


#5

Sometimes mem::replace is used to work around such situations:


#6

I didn’t present it very clearly; the closure should calculate both the new state and an additional return value that should be returned from do_stuff. take_mut requires the closure to be FnOnce(T) -> T, so there’s no straightforward way to return an additional value. I could use some captured reference for that, but that would make everything more complicated.


#7

Ah, I see. Maybe there’s a way to refactor the code to work around that cleanly but you know best …