We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Ownership II: Borrowing and the Borrow Checker step 16 of 24
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 set that second-half element to zero. Return how many pairs were folded.
With n = xs.len() and half = n / 2 (integer division, rounding down):
- the right half is the last
halfelements,xs[n - half ..]; - the left half is everything before it,
xs[.. n - half]; - pair
i, forifrom0tohalf - 1, isxs[i]withxs[n - half + i]; - when
nis odd, the left half is one element longer. Its last element, indexhalf(the exact middle), has no partner and keeps its value; - the return value is
half.
Empty and one-element slices fold nothing and return 0. Values may be
negative. The tests only use inputs where every sum fits in i64, so you do
not need to handle overflow.
[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, []
In the first example, 1 + 3 = 4 and 2 + 4 = 6, and the right half becomes
zeros. In the second, 1 + 4 = 5 and 2 + 5 = 7, and the middle 3 is left
alone.
Why the starter does not compile
The starter holds two mutable references into the slice at the same time:
let dst = &mut xs[i];
let source = &mut xs[n - half + i];
*dst += *source;
The compiler rejects it with E0499 ("cannot borrow xs[_] as mutable more
than once at a time"). The _ in that message shows how the borrow checker
sees an indexed place: it does not compare index values, so it treats any
two xs[...] borrows as possibly the same element, even though i and
n - half + i are different positions here. The diagnostic's help line
suggests split_at_mut, the method this exercise is about. (Item 3.7
covers this error in more detail.)
A function whose signature promises disjointness
The standard library provides a method on slices for exactly this situation:
pub fn split_at_mut(&mut self, mid: usize) -> (&mut [T], &mut [T])
It takes one mutable borrow of the slice and returns two mutable slices:
[0, mid) and [mid, len). It panics if mid > len. Both results borrow
from the original slice, so the original cannot be used while either is
still in use.
The two output slices never overlap. The caller's borrow check relies on the
signature, not on that fact: at a call site the compiler checks your code
against the types of the function, and it does not inspect the function's
body. Inside the standard library, the implementation uses unsafe code to
build the two slices, and its authors are responsible for showing that they
cannot overlap. Callers get a safe API and write no unsafe themselves.
This is a useful pattern to look for when the borrow checker rejects code
that you know touches separate data. Before reaching for unsafe or a
runtime-checked type such as RefCell, check whether a standard-library
method already returns the separate borrows you need.
Related slice methods
Each of these returns mutable access to parts of a slice that do not overlap:
| method | what it gives you |
|---|---|
split_at_mut(mid) |
two halves; panics if mid > len |
split_at_mut_checked(mid) |
the same, returning None instead of panicking (stable since Rust 1.80) |
split_first_mut() / split_last_mut() |
one element and the rest, or None for an empty slice |
chunks_mut(size) / chunks_exact_mut(size) |
consecutive blocks of size elements; panics if size is 0 |
rchunks_mut(size) |
blocks counted from the end |
iter_mut() |
a mutable reference to each element in turn |
get_disjoint_mut([i, j]) |
references at arbitrary indices, returning an error if an index is out of bounds or repeated (stable since Rust 1.86) |
More than one of these can solve this problem. get_disjoint_mut can fetch
each pair separately. chunks_mut(n - half) yields the left half and then
the right half, but it panics on an empty slice because the chunk size would
be 0. split_at_mut is the most direct fit.
Choosing the split point
Split at n - half, not at half. With xs.split_at_mut(n - half), the
middle element of an odd-length slice ends up at the end of the left slice.
With split_at_mut(half), it ends up at the start of the right slice
instead, and the wrong pairs are folded: [1,2,3,4,5] would become
[4,6,0,0,5].
After splitting, left.iter_mut().zip(right.iter_mut()) walks the pairs.
zip stops when the shorter iterator ends. The right half is never longer
than the left, so the middle element is skipped without a special case.
Grading
The submission must compile, pass the tests and produce no warnings under
cargo clippy -- -D warnings. A plain index loop such as
xs[i] += xs[n - half + i] also compiles, because each index expression
borrows the slice only briefly and no two mutable references are held at
once. The exercise is aimed at the split-based approach, but the tests check
only the result.
Your code, saved here
Sign in to access your accepted solutions and saved draft history. You can keep reading the lesson without an account.
Sign in to your accountStuck?
Rust reference solution
Sign in to track your attempts toward unlocking the reference solution.