Mutating enum variant, leaving its data alone

I have a Vec of objects, each of which have a state, which is an enum:

enum State {
  Active,
  Idle
}

struct Msg {
  state: State,
  // .. other stuff ..
}

let mut v: Vec<Msg> = Vec::new();

It would be nice to be able to wrap the Msg in the State instead:

enum State {
  Active(Msg),
  Idle(Msg)
}

The only reason this wasn't done is because Msg is quite large, state changes can happen very often, and I didn't want to deal with the overhead of copying Msg each time. I've started making plans to Box the Msg.

While thinking about alternatives it made me wonder if there is any way to mutate only the enum variant, but leaving its data untouched? I can't imagine this is possible (at least not in a safe manner) -- but could Rust be made to allow this to be done (if the data is of the exact same type in the variants being switched)?

The compiler actually does something like this internally for async future state transitions, but unfortunately, there’s no way to access it directly.

There's a good chance that if you do the obvious assignment,

match self {
    State::Active(msg) => self = State::Idle(msg),
}

then the move will be optimized away, but it’s not guaranteed.

If you want to approach it with unsafe code, you will need an enum with guaranteed layout:

#[repr(C, u8)]
enum State {
  Active(Msg) = 0,
  Idle(Msg) = 1,
}

Given this explicit configuration, you can change the variant by unsafely writing 0 or 1 to the first byte.

#[derive(Debug)]
struct Msg;

#[derive(Debug)]
#[repr(C, u8)]
enum State {
  Active(Msg) = 0,
  Idle(Msg) = 1,
}

fn main() {
    let mut state = State::Active(Msg);
    
    println!("{state:?}");
    unsafe { *(&raw mut state).cast::<u8>() = 1 };
    println!("{state:?}");
}
Active(Msg)
Idle(Msg)

When doing this, you must make sure that the field types match (or that transmuteing from one field type to the other would be safe) and that you don’t write a number that isn't any of the declared discriminants.

3 Likes