0

I have this function that borrows two individual elements from a vector. It works as expected:

fn borrow_mut_two<T>(v: &mut [T], i: usize, j: usize) -> (&mut T, &mut T) {
  assert!(i < j);
  let (left, right) = v.split_at_mut(j);
  (&mut left[i], &mut right[0])
}

fn test() {
  let mut v = vec![0, 1, 2, 3, 4, 5, 6, 7, 8];
  let i = 2;
  let j = 5;
  let (ref_a, ref_b) = borrow_mut_two(&mut v, i, j);

  *ref_a += 1;
  *ref_b += 5;

  assert_eq!(*ref_a, i + 1);
  assert_eq!(*ref_b, j + 5);
}

What I'm trying to understand is why the following code doesn't compile:

fn test() {
  let mut v = vec![0, 1, 2, 3, 4, 5, 6, 7, 8];
  let i = 2;
  let j = 5;
  let (ref_a, ref_b) = borrow_mut_two(&mut v, i, j);

  // Added this line:
  let (other_ref_a, other_ref_b) = borrow_mut_two(&mut v, i, j);

  *ref_a += 1;
  *ref_b += 5;

  assert_eq!(*ref_a, i + 1);
  assert_eq!(*ref_b, j + 5);
}

It seems it works in a safe manner, because it doesn't allow me to mutably borrow the same elements twice (or potentially other elements).

My question is how does the compiler know that this is unsafe (and therefore reject compiling it)?

The compiler errors are:

229 |     let (ref_a, ref_b) = borrow_mut_two(&mut v, i, j);
    |                                         ------ first mutable borrow occurs here
230 |     let (other_ref_a, other_ref_b) = borrow_mut_two(&mut v, i, j);
    |                                                     ^^^^^^ second mutable borrow occurs here

As far as I know the return value of borrow_mut_two is the tuple (&mut T, &mut T), which may or may not be a borrow to self, but it seems the compiler does know it's borrowing self. My assumptions may be wrong though .

The only thing that comes to mind is that Rust automatically adds the lifetime:

fn borrow_mut_two<'a, T>(v: &'a mut [T], i: usize, j: usize) -> (&'a mut T, &'a mut T)

Which would mean that in my test function, 'a is still alive (i.e. self mutably borrowed) due to the first call to borrow_mut_two while the second call happens, and that's how it detects a second mutable borrow.

But I'd like to confirm if this is correct.

Extra

It could be related to this part of the book. The only difference I see is that my function returns a tuple. So the question can be reduced to: Does Rust's second lifetime elision rule also add 'a to each element of a tuple? If so, then my doubt is solved:

The first rule is that the compiler assigns a lifetime parameter to each parameter that’s a reference. In other words, a function with one parameter gets one lifetime parameter: fn foo<'a>(x: &'a i32); a function with two parameters gets two separate lifetime parameters: fn foo<'a, 'b>(x: &'a i32, y: &'b i32); and so on.

The second rule is that, if there is exactly one input lifetime parameter, that lifetime is assigned to all output lifetime parameters: fn foo<'a>(x: &'a i32) -> &'a i32.

Chris Vilches
  • 986
  • 2
  • 10
  • 25

2 Answers2

2

Yes, your understanding is correct - due to lifetime elision, the following two function signatures are equivalent:

fn borrow_mut_two<T>(v: &mut [T], i: usize, j: usize) -> (&mut T, &mut T)
fn borrow_mut_two<'a, T>(v: &'a mut [T], i: usize, j: usize) -> (&'a mut T, &'a mut T)

Because the lifetimes of the output are tied to the lifetime of v, the compiler knows that v is still mutably borrowed until those derived references stop being used.

Joe Clay
  • 33,401
  • 4
  • 85
  • 85
0

The issue in your second code is that you are trying to borrow mutably v while an other mutable borrow is still alive, just as the compiler indicates to you. To be more precise, the compiler sees the following constraints:

fn test() {
  // ...
  let (ref_a, ref_b) = borrow_mut_two(&'3 mut v, i, j);              // ------+
  //   ^^^^^  ^^^^^                   ^^^^^^^ Let's call             //       |
  //       +---+                              this lifetime '3       //       |
  //       |                                                         //       |
  //       Let's call the lifetime of these mutable borrows '1       //       |
  // Added this line:                                                //       +- '1
  let (other_ref_a, other_ref_b) = borrow_mut_two(&'4 mut v, i, j);  // -+ '2 |
  //   ^^^^^^^^^^^  ^^^^^^^^^^^                                      //  |    |
  //           +------+ Let's call the lifetimes of these borrows '2 // -+    |
  *ref_a += 1;                                                       //       |
  *ref_b += 5;                                                       // ------+
}

First of all, '3 and '4 cannot outlive the lifetime of v, but this isn't very important in this case so we can forget about it. Then, due to the signature of borrow_mut_two, '1='3 and '2='4. Furthermore, since we are talking about mutable borrows, there can be at most one at a time. Finally, I have indicated the minimum span of '1 and '2.

When the compiler tries to enforce all these constraints, it will fail, because clearly '1 and '2 cannot be disjoint while living at least for as much time as they are useful.

In fact, it is sometimes possible to make this kind of layout work, because '1 is never used when '2 is used, and '2's span is included in '1's. In these cases, the compiler says the borrows are stacked, and it could accept them if '2 was a reborrow of '1. It is logically the case, but split_at_mut prevents the compiler from understanding this because internally it uses unsafe code on which the compiler can't reason as easily as it would otherwise.

jthulhu
  • 7,223
  • 2
  • 16
  • 33