🎓 A1Learner
🔍
0 of 149 lessons completed

Rust Splitting Borrows

Splitting borrows means holding two mutable borrows of two different halves of one value. The standard library builds one small unsafe escape hatch for this split.

The problem: two mutable halves

Borrowing two different ranges of one Vec looks harmless. The checker refuses it anyway, with error E0499:

let left = &mut scores[0..2];
let right = &mut scores[2..4]; // error E0499: borrowed as mutable more than once

The standard library can do it

A slice has a method that does exactly this split. The method is split_at_mut, an ordinary safe method:

let (left, right) = scores.split_at_mut(2);

Splitting one item at a time

split_first_mut peels the first item off, keeping the rest as one borrow. Repeat it, and you walk the whole slice, holding each borrow separately — this is the engine inside iter_mut:

match slice.split_first_mut() {
    Some((first, rest)) => { *first *= 2; slice = rest; }
    None => break,
}

Swap any two items

To swap the items at positions lo and hi, where lo < hi: split at hi. The lower item is inside head, and the higher one is the first item of tail:

let (head, tail) = slice.split_at_mut(hi);
std::mem::swap(&mut head[lo], &mut tail[0]);

Split wherever a test says

split_mut cuts the slice at every item where a test is true, and hands you each piece mutably. The items the test matched are the cut points, not part of any piece:

let mut nums = vec![1, 0, 2, 0, 3];
for part in nums.split_mut(|n| *n == 0) {
    for n in part { *n += 10; }
}
println!("{:?}", nums);

Pick scattered items, mutably

iter_mut has already proved the items disjoint. You may collect several &mut at once and hold them together. No index arithmetic can convince the checker of that:

let picked: Vec<&mut i32> = v.iter_mut().enumerate()
    .filter(|(i, _)| i % 2 == 0)
    .map(|(_, x)| x)
    .collect();

Example

fn split_at_mut(values: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
    let len = values.len();
    let ptr = values.as_mut_ptr();
    assert!(mid <= len);
    unsafe {
        // SAFETY: mid was checked against len, so both halves lie inside
        // the one slice and never overlap.
        (
            std::slice::from_raw_parts_mut(ptr, mid),
            std::slice::from_raw_parts_mut(ptr.add(mid), len - mid),
        )
    }
}
fn main() {
    let mut scores = vec![10, 20, 30, 40];
    let (left, right) = split_at_mut(&mut scores, 2);
    for n in left.iter_mut() { *n += 1; }
    for n in right.iter_mut() { *n += 100; }
    println!("scores = {:?}", scores);
}

Key points

  • Inside the standard library, the split is built with one raw pointer plus slice::from_raw_parts_mut, inside a single small unsafe block
  • The SAFETY argument is short and strong.
  • mid is checked against the length first.
  • Both halves lie inside the original slice.
  • They never overlap
  • Use the standard split_at_mut in your own code — write your own only when you are building a new collection type
  • Splitting borrows gives two halves of one value, each borrowed mutably at the same time.
  • This is the pattern behind most safe abstractions.
  • The borrow checker cannot see the proof.
  • A human writes it once, in one small place.
  • Everyone else calls the safe version.
  • Every split hands the checker a proof it accepts.
  • The normal rule allows only one &mut borrow at a time.

In this example, split_at_mut is rebuilt by hand, the way the standard library builds it. First it saves the length and takes the slice's raw pointer with as_mut_ptr. The assert checks mid against the length — that check is the whole safety proof. Inside unsafe, from_raw_parts_mut builds the first slice from the pointer with mid items, and the second from ptr.add(mid) with the rest. The two halves cannot overlap, because the second starts exactly where the first ends. Back in main, the left half gains 1 per item. The right half gains 100. The line prints scores = [11, 21, 130, 140]. A free function is written here so you can see the machinery. Real code calls the built-in method.

Note

If you catch yourself doing index juggling to please it, look for a split method first — there usually is one.

Try it Yourself

Edit the code, then press Run.

Running is simulated · Rust Playground

Exercise

Why is the unsafe block inside split_at_mut sound?