#230 Jun 29, 2026

230. slice::split_at_mut — Two Mutable Halves, No unsafe, No Borrow Fight

Need a &mut to two different elements of the same slice at once? Indexing twice won’t compile — the borrow checker sees the whole slice borrowed twice. split_at_mut hands you two non-overlapping mutable halves instead.

The borrow checker says no

You want to mutate two elements of a slice in the same expression — say, swap-style logic that reads one and writes another:

1
2
3
4
let mut v = [1, 2, 3, 4];
let a = &mut v[0];
let b = &mut v[3]; // error: cannot borrow `v` as mutable more than once
*a += *b;

The compiler can’t prove v[0] and v[3] are different elements, so it rejects two overlapping &mut borrows of v. Both indexing operations borrow the entire slice.

split_at_mut gives you two disjoint slices

split_at_mut(mid) splits a slice into [0, mid) and [mid, len), returning a &mut to each. Because the halves provably don’t overlap, the borrow checker is happy:

1
2
3
4
let mut v = [1, 2, 3, 4];
let (left, right) = v.split_at_mut(2);
left[0] += right[1]; // v[0] += v[3]
assert_eq!(v, [5, 2, 3, 4]);

Internally it’s one bounds check and a pointer offset — no copying, no allocation. It panics only if mid > len; use split_at_mut_checked to get an Option instead.

Where it shines: in-place algorithms

Reversing a slice by hand needs a read and a write to two indices each step. Split once, walk inward:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
fn reverse<T>(s: &mut [T]) {
    let n = s.len();
    let (front, back) = s.split_at_mut(n / 2);
    for (a, b) in front.iter_mut().zip(back.iter_mut().rev()) {
        std::mem::swap(a, b);
    }
}

let mut data = [1, 2, 3, 4, 5];
reverse(&mut data);
assert_eq!(data, [5, 4, 3, 2, 1]);

The two iterators borrow disjoint halves, so iterating both mutably at once just works.

Divide and conquer for free

Because each half is itself a &mut [T], recursive algorithms split cleanly — the basis for parallelizing with something like Rayon’s join:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
fn sum_halves(s: &mut [i32]) -> i32 {
    if s.len() <= 1 {
        return s.first().copied().unwrap_or(0);
    }
    let mid = s.len() / 2;
    let (left, right) = s.split_at_mut(mid);
    sum_halves(left) + sum_halves(right)
}

let mut v = [1, 2, 3, 4, 5];
assert_eq!(sum_halves(&mut v), 15);

Whenever you’re fighting the borrow checker over two mutable spots in one slice, stop reaching for unsafesplit_at_mut is the safe, zero-cost answer. For non-adjacent individual elements, its cousin get_disjoint_mut does the same trick by index.

← Previous 229. char::to_digit — Turn a Digit Character Into Its Value, Not Its Byte Next → 231. slice::chunk_by — Group Consecutive Elements Without the Index Bookkeeping