99. std::mem::replace — Swap a Value and Keep the Old One
mem::take is great until your type doesn’t have a sensible Default. That’s where mem::replace steps in — you pick what gets left behind, and you still get the old value out of a &mut.
The shape of the problem
You can’t move a value out of a &mut T. The borrow checker rightly refuses. mem::take fixes this by swapping in T::default(), but an enum with no obvious default, or a type that deliberately doesn’t implement Default, leaves you stuck.
mem::replace(dest, src) is the escape hatch: it writes src into *dest and hands you back the old value.
| |
No clones, no unsafe, no Default required.
State machines without a default variant
This is where replace earns its keep. Picture a connection type where none of the variants makes a natural default — Disconnected is fine here, but it might be Error(e) somewhere else, and #[derive(Default)] would be a lie:
| |
You get the owned String out of the Connected variant — no cloning the session, no Option<Connection> gymnastics, no unsafe.
Flushing a buffer with a fresh one
mem::take would leave behind an empty Vec with zero capacity. mem::replace lets you pre-size the replacement, which matters if you’re about to refill it:
| |
Same trick works for swapping in a String::with_capacity(...), a pre-allocated HashMap, or anything where the replacement’s shape is tuned for what comes next.
When to reach for which
mem::take when the type has a cheap, meaningful Default and you don’t care about the leftover. mem::replace when you need to control the replacement — an enum variant, a pre-sized collection, a sentinel value. Both are safe, both are O(1), and both read more clearly than the Option::take / unwrap dance.