We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Ownership I: Moves, Copy, Clone, Drop step 14 of 20
Moving out of &mut: take, replace, swap
Two exercises in the same technique: moving a value out of a place you only
have a &mut to.
pub fn rotate3(a: String, b: String, c: String) -> Vec<String>
pub fn drive(ops: Vec<String>) -> Vec<String>
The problem this solves
Everything so far has been a rule. This is the first genuinely non-obvious technique, and it is the answer to a whole family of complaints that sound like the compiler is being unreasonable:
error[E0507]: cannot move out of `self.pending` which is behind a mutable reference
|
| let items = self.pending;
| ^^^^^^^^^^^^ move occurs because `self.pending` has type
| `Vec<String>`, which does not implement `Copy`
The rule being enforced is one you already know: a place must always hold a
valid value. You do not own self; you have borrowed it, and you must give it
back intact. Moving the Vec out would leave a hole in something the caller still
owns.
Which points straight at the solution. If the objection is “you would leave a hole”, then do not leave a hole — put something else there. That is all these three functions do:
std::mem::swap(&mut a, &mut b) // exchange the contents of two places
std::mem::replace(&mut place, new) // put `new` in, get the old one out
std::mem::take(&mut place) // replace(&mut place, Default::default())
None of them allocate. swap is three memcpys of the value’s stack bytes;
take on a Vec writes the three words of an empty Vec, which has a dangling
pointer, zero length and zero capacity and touches no heap at all. This is how
real Rust implements iterators, state machines and linked structures without
cloning, and it is why Option::take from two problems ago worked.
Part 1: rotate3
Return [c, a, b] — each string moved one place to the right, wrapping — using
only mem::swap. Two swaps are enough.
The starter does not compile:
error[E0596]: cannot borrow `a` as mutable, as it is not declared as mutable
&mut x requires x to be a mut binding, and function parameters are
bindings like any other. fn rotate3(mut a: String, ...) fixes it. Note that
mut on a parameter is entirely a statement about the local binding: the caller
gave you the value and does not care what you do with it.
::: question Work out where each value is after each swap. Start from a = A, b = B, c = C.
start a = A b = B c = C
swap(&mut a, &mut b) a = B b = A c = C
swap(&mut a, &mut c) a = C b = A c = B
and vec![a, b, c] is [C, A, B]. Correct.
Worth noticing what mem::swap is not: it is not let t = a; a = b; b = t;.
That sequence needs a temporary that owns a for an instant, which is fine for
locals but impossible when the places are behind references — you would be
moving out of a borrow to build t. swap never has an intermediate owner; it
exchanges the bytes in place, which is why it works on &mut and why it is the
primitive the other two are built from.
:::
Part 2: drive
struct Buffer {
pending: Vec<String>,
flushed: usize,
}
Write Buffer::flush(&mut self) -> String: it must empty pending, return its
contents joined with ",", and increment flushed. Then drive runs a script:
-
"push <text>"— append<text>topending -
"flush"— callflushand record what it returned - anything else — ignore
The return value is every flush output in order, followed by one final line
format!("flushes={}|pending={}", buf.flushed, buf.pending.len())
reporting how many flushes happened and how many items are still pending. So
["push a", "push b", "flush"] gives ["a,b", "flushes=1|pending=0"], and
["push a", "flush", "flush"] gives ["a", "", "flushes=2|pending=0"].
That second one is the assertion that matters: the trailing flush emits the
empty string, which proves pending was genuinely emptied by the first
flush rather than merely read. And pending=0 in the summary proves the same
thing from the other side.
The lint that teaches the idiom
Write the flush like this and it compiles perfectly:
let items = std::mem::replace(&mut self.pending, Vec::new());
and clippy refuses it:
error: replacing a value of type `T` with `T::default()` is better expressed
using `std::mem::take`
help: consider using: `std::mem::take(&mut self.pending)`
mem_replace_with_default is on by default, so a learner who reaches for
the longhand is told the short form exists, by name, at the moment they need it.
There is no better way to learn a standard-library idiom than being handed it
the first time you almost need it.
Its siblings are worth knowing too: mem_replace_option_with_none (use
.take()), mem_replace_option_with_some (use .replace(v)), and the
correctness-level mem_replace_with_uninit, which catches genuinely unsound
code.
Remember the grade is compile + tests + clippy -D warnings.
Loading visualization…
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.