Reputation: 3701
In this github discussion you find this code that draws the ire of the borrow checker:
fn main() {
let mut vec = vec!();
match vec.first() {
None => vec.push(5),
Some(v) => unreachable!(),
}
}
I understand why having a mutation while immutable borrows are outstanding is problematic. I assumed that a solution was to explicitly only have one borrow (a mutable one) but it still resulted in my having two borrows, an immutable borrow and then a mutable borrow:
fn main() {
let mut vec: Vec<i32> = vec!();
let r_vec: &mut Vec<i32> = &mut vec;
match r_vec.first() {
None => r_vec.push(5),
Some(v) => unreachable!(),
}
}
The compiler is still not happy:
error[E0502]: cannot borrow `*r_vec` as mutable because it is also borrowed as immutable
--> testrust.rs:7:17
|
6 | match r_vec.first() {
| ----- immutable borrow occurs here
7 | None => r_vec.push(5),
| ^^^^^ mutable borrow occurs here
8 | Some(v) => unreachable!(),
9 | }
| - immutable borrow ends here
Why does my workaround not work, and what is the proper way to get around this issue?
Upvotes: 4
Views: 996
Reputation: 432199
You don't. Well, you "avoid" having multiple borrows by... not having multiple borrows.
fn main() {
let mut vec = vec![];
if vec.first().is_none() {
vec.push(5);
}
}
Even more idiomatically:
if vec.is_empty() {
vec.push(5);
}
In both cases, we borrow vec
to make the method call, but terminate that borrow before the body of the if
is executed. Compare that to the match
where the borrow is made in the match head expression, and then shared with the match arms.
take one mutable borrow that can be used for both cases
That's not how it works. You have to understand how memory plays out and what a reference is. A Vec
contains a pointer to memory where the data is stored.
When you get a reference to data the vector, the reference holds the address of the memory for the data, and the compiler ensures there's only one of those allowed to mutate the Vec
. When you push
a value, that may need to allocate new memory to store all the data. This may invalidate the reference you hold. If that were to occur, then the next time you use the reference, it would point to some other, unrelated piece of memory, your program would crash, your users data would be exposed to security vulnerabilities, etc. etc. etc.
The entire point of the issue you linked and the related pre-RFC is that this code should be able to be determined as safe:
match vec.first() {
None => vec.push(5),
Some(v) => unreachable!(),
}
In this case, the programmer can see that we never use the borrow in the None
case, so the the compiler could theoretically end the borrow before executing any of the match arms or otherwise make the two arms disjoint with respect to lifetimes. It does not do that now.
However, in your version of code, it's actually worse. By explicitly taking the borrow and keeping it in a variable, you could be extending how long the borrow needs to stay around, forcing them to overlap.
Currently, the only solution is to reorder your code to artificially constrain borrows. I've not found this very annoying in practice, as usually there's a better organization of code anyway.
See also:
Upvotes: 6