Skip to content

← Ownership II: Borrowing and the Borrow Checker step 16 of 24

Medium Primitives

split_at_mut and the disjointness-proof APIs

pub fn fold_halves(xs: &mut [i64]) -> usize

Fold the second half of the slice into the first: add each element of the second half into the matching element of the first half, then zero the second half. Return how many pairs were folded.

The halves are pinned exactly. With n = xs.len() and half = n / 2:

  • the right half is the last half elements, xs[n - half ..];
  • the left half is everything before it, xs[.. n - half];
  • pair i is xs[i] with xs[n - half + i], in order;
  • when n is odd, the left half is one longer, and its last element — index half, the exact middle — has no partner and is left untouched;
  • the return value is half.
[1,2,3,4]    -> 2, [4,6,0,0]
[1,2,3,4,5]  -> 2, [5,7,3,0,0]     <- index 2 (value 3) is the untouched middle
[10,20,30]   -> 1, [40,20,0]       <- index 1 (value 20) is the untouched middle
[9]          -> 0, [9]
[]           -> 0, []

The starter does not compile: E0499, two exclusive loans of the same slice.

The cleanest possible statement of the thesis

You know from item 3.7 that &mut xs[i] and &mut xs[j] cannot coexist, because both are loans of xs as far as the type system can see, and the compiler has no theory of arithmetic that would separate them.

Here is the fix, and it is worth staring at:

pub fn split_at_mut(&mut self, mid: usize) -> (&mut [T], &mut [T])

One exclusive reference goes in. Two come out. Both have the same lifetime as the input. And the whole thing is in core, safe to call, no unsafe at your call site.

Read what that signature asserts: these two slices do not overlap. The compiler does not verify that claim from the body — it never reads bodies. It takes the signature as the contract. Somebody wrote a proof, once, inside split_at_mut (with unsafe, and a // SAFETY: comment explaining why mid partitions the buffer), and every caller since has consumed it for free.

So the compiler did not get smarter. Somebody wrote the proof down in a type. That is the single most useful idea in this track, and once you have it, “the borrow checker won’t let me” stops being a dead end and becomes a search query.

Learners who do not have this idea reach for unsafe or RefCell prematurely, because those are the two escape hatches that are easy to find. Both are the wrong first move. The right first move is: which standard-library function already returns the shape I need?

The family

Every one of these has a signature that encodes a disjointness proof. Learn the shapes; you will reach for them constantly.

function proof it carries
split_at_mut(mid) two non-overlapping halves
split_at_mut_checked(mid) same, Option instead of a panic (1.80)
split_first_mut() / split_last_mut() one element and the rest
chunks_mut(n) / chunks_exact_mut(n) consecutive non-overlapping blocks
rchunks_mut(n) the same, counted from the end
iter_mut() one element at a time, never revisited (item 3.14)
get_disjoint_mut([i, j]) arbitrary indices, checked at runtime (1.86)

For this problem, split_at_mut is the obvious fit — but notice that get_disjoint_mut would also work, one pair at a time, and chunks_mut would not, because your two halves are not the same length when n is odd.

A word on picking mid. xs.split_at_mut(n - half) puts the middle element (when there is one) at the end of the left slice, which is exactly what the spec wants: it never gets a partner. Splitting at half instead would put the middle at the front of the right slice and fold the wrong pairs. Check your arithmetic against the odd-length case before you run anything.

Then left.iter_mut().zip(right.iter_mut()) walks the pairs, and zip stops at the shorter side — which is the right half — so the middle is skipped for free.

Two lints watching

needless_range_loop if you index; manual_memcpy if you write a loop that is really a bulk copy (copy_from_slice exists); manual_swap if you write the three-line temporary-variable exchange rather than slice::swap.

Remember the grade is compile + tests + clippy -D warnings.

Loading visualization…