arraysrustmutable

How to get mutable references to two array elements at the same time?


fn change(a: &mut i32, b: &mut i32) {
    let c = *a;
    *a = *b;
    *b = c;
}

fn main() {
    let mut v = vec![1, 2, 3];
    change(&mut v[0], &mut v[1]);
}

When I compile the code above, it has the error:

error[E0499]: cannot borrow `v` as mutable more than once at a time
 --> src/main.rs:9:32
  |
9 |         change(&mut v[0], &mut v[1]);
  |                     -          ^   - first borrow ends here
  |                     |          |
  |                     |          second mutable borrow occurs here
  |                     first mutable borrow occurs here

Why does the compiler prohibit it? v[0] and v[1] occupy different memory positions, so it's not dangerous to use these together. And what should I do if I come across this problem?


Solution

  • You can solve this with split_at_mut():

    let mut v = vec![1, 2, 3];
    let (a, b) = v.split_at_mut(1);   // Returns (&mut [1], &mut [2, 3])
    change(&mut a[0], &mut b[0]); 
    

    There are uncountably many safe things to do that the compiler unfortunately does not recognize yet. split_at_mut() is just like that, a safe abstraction implemented with an unsafe block internally.

    We can do that too, for this problem. The following is something I use in code where I need to separate all three cases anyway (I: Index out of bounds, II: Indices equal, III: Separate indices).

    enum Pair<T> {
        Both(T, T),
        One(T),
        None,
    }
    
    fn index_twice<T>(slc: &mut [T], a: usize, b: usize) -> Pair<&mut T> {
        if a == b {
            slc.get_mut(a).map_or(Pair::None, Pair::One)
        } else {
            if a >= slc.len() || b >= slc.len() {
                Pair::None
            } else {
                // safe because a, b are in bounds and distinct
                unsafe {
                    let ar = &mut *(slc.get_unchecked_mut(a) as *mut _);
                    let br = &mut *(slc.get_unchecked_mut(b) as *mut _);
                    Pair::Both(ar, br)
                }
            }
        }
    }