It is very reasonable to ask this question, in fact, for a long time, the rust compiler would have agreed with your assertion:
error[E0499]: cannot borrow `a` as mutable more than once at a time
--> <source>:12:20
|
11 | let mut_val1 = a.as_mut();
| - first mutable borrow occurs here
12 | let mut_val2 = a.as_mut();
| ^ second mutable borrow occurs here
13 | }
| - first borrow ends here
But by now, with a capability called “non-lexical lifetimes”, this code is allowed. The idea is to behave, for the purposes of borrow-checking, as-if the scope of local variables like mut_val1
is shorter than it actually is. The mut_val1
variable is technically only dropped after the second a.as_mut()
, but we can behave as-if it was dropped because
- dropping
&mut A<'_>
is a no-op, so (behaving as-if we’re) dropping earlier doesn’t change observable behavior
- the variable
mut_val1
is no longer used by the time of the second a.as_mut()
call, so behaving as-if we’re dropping it earlier will not introduce any use-after-free problems
“used” in this case of course refers not only to direct mentions, but also some transitive usage by using other re-borrows. E.g. while
fn test(val: &mut u32) {
let mut a = A { val };
let mut_val1 = a.as_mut();
let borrow = &mut_val1;
let mut_val2 = a.as_mut();
}
works, in
fn test(val: &mut u32) {
let mut a = A { val };
let mut_val1 = a.as_mut();
let borrow = &mut_val1;
let mut_val2 = a.as_mut();
println!("{borrow}");
}
the usage of borrow
also effectively is, indirectly, a usage of mut_val1
:
error[E0499]: cannot borrow `a` as mutable more than once at a time
--> src/lib.rs:13:20
|
11 | let mut_val1 = a.as_mut();
| ---------- first mutable borrow occurs here
12 | let borrow = &mut_val1;
13 | let mut_val2 = a.as_mut();
| ^^^^^^^^^^ second mutable borrow occurs here
14 | println!("{borrow}");
| ------ first borrow later used here
note how the “later used here” of the borrow created when defining mut_val1
is pointing to a usage of borrow
.
Similarly, the condition about destructors needing to be no-ops is also a special-case of the general “usage” rule, if you will, since a custom destructor (i.e. Drop
impl) would arguably use the variable.
In case of custom types like yours, this has the observable consequence that adding a Drop
impl for A<'_>
will make this code fail to compile:
// compiles
struct A<'a> {
val: &'a mut u32,
}
impl<'a> A<'a> {
fn as_mut(&mut self) -> &mut u32 {
self.val
}
}
fn test(val: &mut u32) {
let a = A { val };
let b = A { val };
}
// fails
struct A<'a> {
val: &'a mut u32,
}
impl<'a> A<'a> {
fn as_mut(&mut self) -> &mut u32 {
self.val
}
}
impl Drop for A<'_> {
fn drop(&mut self) {}
}
fn test(val: &mut u32) {
let a = A { val };
let b = A { val };
}