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.
1
2
3
4
5
6
7
| use std::mem;
let mut greeting = String::from("Hello");
let old = mem::replace(&mut greeting, String::from("Howdy"));
assert_eq!(old, "Hello");
assert_eq!(greeting, "Howdy");
|
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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| use std::mem;
enum Connection {
Disconnected,
Connecting(u32),
Connected { session: String },
}
fn finalize(conn: &mut Connection) -> Option<String> {
match mem::replace(conn, Connection::Disconnected) {
Connection::Connected { session } => Some(session),
_ => None,
}
}
let mut c = Connection::Connected { session: String::from("abc123") };
let session = finalize(&mut c);
assert_eq!(session.as_deref(), Some("abc123"));
assert!(matches!(c, Connection::Disconnected));
|
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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| use std::mem;
struct Batch {
items: Vec<u32>,
}
impl Batch {
fn flush(&mut self) -> Vec<u32> {
mem::replace(&mut self.items, Vec::with_capacity(16))
}
}
let mut b = Batch { items: vec![1, 2, 3] };
let drained = b.flush();
assert_eq!(drained, vec![1, 2, 3]);
assert!(b.items.is_empty());
assert_eq!(b.items.capacity(), 16);
|
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.