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: fnfoo<'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
.