197. Advance a State Machine with mem::replace — Move the Enum Out, No Clone
Transitioning an enum state behind &mut self looks impossible: you can’t move the old variant’s owned data into the new one without the borrow checker stopping you — so people reach for .clone(). mem::replace lets you move the whole state out, leaving a cheap placeholder behind.
This closes out the performance week. Earlier bites covered mem::take, mem::replace, and mem::swap as primitives. Here’s the pattern they were built for: a state machine that moves owned data forward through its transitions.
The setup
A job that walks Queued → Running → Done, carrying an owned String payload from one state into the next:
| |
The trap: matching a borrow forces a clone
You only have &mut self, so the obvious move is to match on &self.stage. But that gives you a borrow of payload — to put it in the next state you have to clone it:
| |
Matching on self.stage by value would move out of &mut self — the borrow checker rejects it outright. So clone feels like the only way out. It isn’t.
The fix: replace the whole state, then match by value
mem::replace swaps in a cheap placeholder and hands you the real state by value. Now the match owns payload and can move it straight into the next variant — zero clones:
| |
The placeholder (Done { result: String::new() }) is free — an empty String allocates nothing — and it lives for only the instant before you overwrite self.stage with the match result.
| |
The payload string is allocated once and threaded through all three states by pointer. No copy of the bytes ever happens — exactly what the clone-based version threw away on every transition.