Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

rust

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?

like image 505
user2925565 Avatar asked May 06 '15 10:05

user2925565


5 Answers

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)             }         }     } } 
like image 135
bluss Avatar answered Sep 17 '22 16:09

bluss


Since Rust 1.26, pattern matching can be done on slices. You can use that as long as you don't have huge indices and your indices are known at compile-time.

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

fn main() {
    let mut arr = [5, 6, 7, 8];
    {
        let [ref mut a, _, ref mut b, ..] = arr;
        change(a, b);
    }
    assert_eq!(arr, [7, 6, 5, 8]);
}
like image 45
oli_obk Avatar answered Sep 20 '22 16:09

oli_obk


The borrow rules of Rust need to be checked at compilation time, that is why something like mutably borrowing a part of a Vec is a very hard problem to solve (if not impossible), and why it is not possible with Rust.

Thus, when you do something like &mut v[i], it will mutably borrow the entire vector.

Imagine I did something like

let guard = something(&mut v[i]);
do_something_else(&mut v[j]);
guard.do_job();

Here, I create an object guard that internally stores a mutable reference to v[i], and will do something with it when I call do_job().

In the meantime, I did something that changed v[j]. guard holds a mutable reference that is supposed to guarantee nothing else can modify v[i]. In this case, all is good, as long as i is different from j; if the two values are equal it is a huge violation of the borrow rules.

As the compiler cannot guarantee that i != j, it is thus forbidden.

This was a simple example, but similar cases are legions, and are why such access mutably borrows the whole container. Plus the fact that the compiler actually does not know enough about the internals of Vec to ensure that this operation is safe even if i != j.


In your precise case, you can have a look at the swap(..) method available on Vec that does the swap you are manually implementing.

On a more generic case, you'll probably need an other container. Possibilities are wrapping all the values of your Vec into a type with interior mutability, such as Cell or RefCell, or even using a completely different container, as @llogiq suggested in his answer with par-vec.

like image 32
Levans Avatar answered Sep 17 '22 16:09

Levans


The method [T]::iter_mut() returns an iterator that can yield a mutable reference for each element in the slice. Other collections have an iter_mut method too. These methods often encapsulate unsafe code, but their interface is totally safe.

Here's a general purpose extension trait that adds a method on slices that returns mutable references to two distinct items by index:

pub trait SliceExt {
    type Item;

    fn get_two_mut(&mut self, index0: usize, index1: usize) -> (&mut Self::Item, &mut Self::Item);
}

impl<T> SliceExt for [T] {
    type Item = T;

    fn get_two_mut(&mut self, index0: usize, index1: usize) -> (&mut Self::Item, &mut Self::Item) {
        match index0.cmp(&index1) {
            Ordering::Less => {
                let mut iter = self.iter_mut();
                let item0 = iter.nth(index0).unwrap();
                let item1 = iter.nth(index1 - index0 - 1).unwrap();
                (item0, item1)
            }
            Ordering::Equal => panic!("[T]::get_two_mut(): received same index twice ({})", index0),
            Ordering::Greater => {
                let mut iter = self.iter_mut();
                let item1 = iter.nth(index1).unwrap();
                let item0 = iter.nth(index0 - index1 - 1).unwrap();
                (item0, item1)
            }
        }
    }
}
like image 25
Francis Gagné Avatar answered Sep 20 '22 16:09

Francis Gagné


You can't make two mutable references to the same data. This is something explicitly forbidden by the borrow checker, to prevent concurrent modifications. However you can bypass the borrow checker by using unsafe blocks.

While in your case v[0] and v[1] are clearly separate chunks, that doesn't stand to serious scrutiny. What if v is some kind of map called NullMap that maps all elements to a single field? How will compiler know in a Vec operationsv[0];v[1]; is safe but in NullMap isn't?


If you are trying to swap two elements of an array, why not go for slice::swap?

fn main() {
    let mut v = vec![1, 2, 3];
    v.swap(0,1);
    println!("{:?}",v);
}

Also v needs to be mut, because you are changing vector. An immutable version would clone and perform a swap on it.

like image 34
Daniel Fath Avatar answered Sep 18 '22 16:09

Daniel Fath