We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Ownership II: Borrowing and the Borrow Checker step 19 of 24
What a &mut parameter actually promises
pub fn merge_into(dst: &mut Vec<i64>, src: &[i64])
pub fn self_merge(xs: &mut Vec<i64>) -> usize
merge_into appends every element of src to dst and then sorts dst
ascending. It is written for you and is correct.
self_merge must merge a vector into itself: the result contains every
original element twice, sorted ascending. It returns the new length.
dst [3,1], src [2] -> merge_into gives [1,2,3]
self_merge then gives [1,1,2,2,3,3], len 6
dst [5], src [] -> [5] then [5,5], len 2
dst [], src [] -> [] then [], len 0
The obvious implementation of self_merge is one line:
merge_into(xs, xs);
It does not compile, and understanding why closes the loop on this whole track.
The promise
error[E0502]: cannot borrow `*xs` as immutable because it is also borrowed as mutable
When you write dst: &mut Vec<i64> you are not saying “I might write to
this”. You are making a promise on behalf of every caller:
For as long as this call runs,
dstis reachable through this reference and nothing else. No other name, no other reference, no other thread can read it or write it.
merge_into(xs, xs) breaks that promise on its face: src would alias dst.
So the compiler rejects the call site — not the function.
And this is not a compiler limitation to be worked around. It is the entire
value proposition. merge_into‘s body reads src while pushing to dst. If
they aliased, dst.extend_from_slice(src) would be reading a buffer that is
being reallocated underneath it. In C++ this is a real bug you have to think
about at every call site; in Rust the signature makes it impossible.
The corollary in machine code
rustc lowers &mut parameters to LLVM’s noalias attribute — the same
thing C’s restrict means, except that in C you promise it by hand and in
Rust the type system proves it.
So the optimiser is entitled to assume no aliasing: it can keep *dst in a
register across a call, skip a reload after a store through an unrelated
pointer, and collapse branches that could only differ if two pointers were
equal. The guarantee is consumed. It is not a style rule you might
reasonably break if you were careful — it has already been spent by codegen,
and being careful is not a substitute. Item 3.23 works through the assembly.
Three legal routes, and what each costs
-
extend_from_within(..)— aVecmethod for exactly this: copy a range of the vector onto its own end. One&mut, no second borrow, no temporary. The copying happens insideVec, where the buffer’s owner can reason about its own reallocation. This is the right answer. -
Copy the source out first. Build an owned snapshot, then merge that in.
Correct, and an extra allocation you did not need. (
.clone()is withdrawn in this track anyway.) -
split_at_mut— split the vector in two provably-disjoint halves and work across them (item 3.16). Overkill here, but the general shape when the two regions really are distinct parts of the same buffer.
All three share the same insight: you cannot hand the same place to a &mut
and a & at once, so make one of them not be that place.
The elision trap, planted now so it does not ambush you later
While you are here, look at this signature and predict what it does to the caller:
fn get(&mut self) -> &Thing
Elision fills in one lifetime for both, so this is really
fn get<'a>(&'a mut self) -> &'a Thing. The returned reference is
immutable, which makes it look harmless — but its lifetime is tied to the
&mut self borrow, so self stays mutably borrowed for as long as the
returned &Thing lives. Hold it in a local and the whole object is frozen
until you drop it.
That is the single most surprising consequence of lifetime elision, and it catches people who have been writing Rust for a year. Track 8 covers the rules properly; for now, just recognise the shape.
The reverse shape is worse and clippy denies it outright:
fn f(x: &T) -> &mut U // error: clippy::mut_from_ref
mut_from_ref is a correctness lint and — uniquely in this whole cluster
— it is deny-by-default, not warn-by-default. Producing an exclusive
reference out of a shared one lets a caller make two &muts to the same place
with no unsafe in sight, which is unsound. It is not hypothetical: a bug of
this shape in the standard library caused the Rust 1.15.1 point release.
Two more lints in the neighbourhood: needless_pass_by_ref_mut (you asked for
&mut and never wrote through it) and unnecessary_mut_passed (you passed
&mut x to something that wanted &x).
About the test cases
The harness asserts the vector after merge_into and before self_merge,
as well as after. Without that intermediate assertion, an implementation that
gets the append order wrong would be hidden by the final sort.
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.