Consuming and reassigning a type via a mutable reference

I was building a state machine and wanted to model a state transition where I change the state, but reuse some of the data of the old state. In other words I wanted to update the variable behind a mutable reference by consuming the original value and creating a new one.

This code basically does it:

#[derive(Debug)]
enum State {
    A { data: Data },
    B { data: Data },
}

impl State {
    fn toggle(&mut self) {
        map_type(self, |s| match s {
            State::A { data } => State::B { data },
            State::B { data } => State::A { data },
        });
    }
}

#[derive(Debug)]
struct Data;

fn map_type<T>(val: &mut T, f: impl FnOnce(T) -> T) {
    unsafe {
        let original = std::ptr::read(val);
        std::ptr::write(val, f(original));
    }
}

I did not find anything similar to map_type in the standard library, so I assume what I am doing is wrong on some degree. But I can't find out what is wrong with this.

What do you think about the code?

Have you seen mem::replace?

Sorry, I meant replace_with.

The problem with doing it yourself is handling panics, which is what replace_with does for you.

4 Likes

Oh, they even have the same example :smile:

But why is it not in the standard library? This seems like a standard use-case and I am hesitant to use a crate, that might be unmaintained

1 Like

Many very useful things, necessary things, are not in the Rust std library. The std library is not meant to cover everything.

This crate is popular and recommended here quite often. There is no reason to avoid crates like this. And it is almost always better to use an established crate than to write and maintain the necessary unsafe code yourself.

However, in this case there is an RFC for adding it to std but it was closed with this explanation.

4 Likes

FWIW, I also have a (perhaps unhealthy) desire to minimize my crate dependencies, so I generally lean towards a pattern like this:

#[derive(Debug, Default)]
enum State {
    #[default] Poisoned,
    A { data: Data },
    B { data: Data },
}

impl State {
    fn toggle(&mut self) {
        *self = match std::mem::take(self) {
            State::A { data } => State::B { data },
            State::B { data } => State::A { data },
            State::Poisoned => unreachable!(),
        };
    }
}

#[derive(Debug)]
struct Data;

It has the unfortunate effect of adding a variant that has to be handled everywhere, but it's entirely safe code and pretty clear what's going on. (If you don't want to declare the poison value as default, you can use mem::replace in place of mem::take.)

6 Likes

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.