Skip to content

← Ownership I: Moves, Copy, Clone, Drop step 14 of 20

Medium Primitives

Moving out of &mut: take, replace, swap

Fix two ownership errors: rotate three owned strings with mem::swap, then empty a borrowed buffer with mem::take. Keep the supplied drive parser.

pub fn rotate3(a: String, b: String, c: String) -> Vec<String>
pub fn drive(ops: Vec<String>) -> Vec<String>

Rotate three strings

rotate3 must return [c, a, b], moving each string one position to the right. Use the supplied two calls to std::mem::swap; do not clone the strings or replace the exercise with a directly reordered vector.

The starter tries to borrow immutable parameter bindings mutably. Declare the three local parameter bindings mut so they can be swapped. The caller still passes owned String values, not mutable references.

Track the contents after each operation:

Start:               a = A, b = B, c = C
Swap a and b:        a = B, b = A, c = C
Swap a and c:        a = C, b = A, c = B
Return vec![a,b,c]:  [C, A, B]

Empty strings, duplicates and Unicode strings follow the same rule. Swapping the String values transfers their existing buffers; it does not clone their text. Constructing the returned Vec is a separate operation.

Empty the buffer and return its contents

The supplied buffer stores pending strings and a flush counter:

struct Buffer {
    pending: Vec<String>,
    flushed: usize,
}

Fix Buffer::flush(&mut self) -> String so it takes the pending vector, leaves an empty vector behind, increments the counter once and returns the previous strings joined with commas. An empty flush returns "" and still increments the counter.

The starter's let items = self.pending; cannot move a non-Copy field out through a borrow of self. The caller still owns the buffer and expects its field to remain usable. mem::take solves this by installing a replacement while returning the old value.

Choose the replacement operation

Operation What remains in the destination Return value
mem::take(&mut place) T::default() Previous value
mem::replace(&mut place, new) new Previous value
mem::swap(&mut a, &mut b) Each place receives the other's value ()

take requires T: Default; replace and swap do not. None of these operations needs to clone the value. replace and swap do not drop the values they transfer. Returned old values are dropped later unless their caller moves or otherwise retains them.

For Vec<String>, the default replacement is an empty vector that does not allocate. Its previous allocation moves with the returned vector, so pending does not retain that capacity. The subsequent join constructs the output string and may allocate. More generally, take calls the type's Default implementation, which can allocate or panic; it is not universally free of either behavior.

Follow the script exactly

drive recognizes these strings:

  • Exactly "flush": record the result of flushing.
  • A string beginning with "push ": append everything after that prefix.
  • Any other string: ignore it.

"push " appends an empty string, but "push" is ignored. Extra spaces in the appended text are preserved. The result contains all flush outputs in order, followed by this summary:

flushes=COUNT|pending=LENGTH

For example, ["push a", "push b", "flush"] returns ["a,b", "flushes=1|pending=0"]. Adding another "flush" appends an empty output and changes the summary to "flushes=2|pending=0". With no operations, return ["flushes=0|pending=0"].

Use the standard idiom

mem::replace(&mut self.pending, Vec::new()) is valid Rust, but Clippy's mem_replace_with_default recommends mem::take(&mut self.pending). This exercise treats that warning as an error. Use replace when you want to install a specific value rather than the default.

Output tests check the rotation and buffer behavior; they do not by themselves prove that values were moved without cloning. Follow the requested operations as well as returning the right values. The solution must compile and pass clippy -D warnings.