In the following code, method m(&muf self) -> &Self returns an immutable reference which is bind to _b. So when a.m(); borrows a for the second time, the borrow checker should fail because a is borrowed both mutable and immutable. But the error message I get is error[E0499]: cannot borrow a as mutable more than once at a time.
struct A;
impl A {
fn m(&mut self) -> &Self {
self
}
}
fn main() {
let mut a = A {};
let _b = a.m();
a.m(); // cannot borrow `a` as mutable more than once at a time
}
Error message
error[E0499]: cannot borrow `a` as mutable more than once at a time
--> src/main.rs:12:5
|
11 | let _b = a.m();
| - first mutable borrow occurs here
12 | a.m();
| ^ second mutable borrow occurs here
13 | }
| - first borrow ends here
No, the error is correct. When you call a.m(), you are mutably borrowing a. That m returns an immutable borrow is irrelevant: the function needed a mutable borrow to construct that immutable borrow, so as long as the returned borrow is active, so is that initial mutable borrow.
My understanding is that borrow ends when the reference goes out of scope. I guess return somehow extends the scope (?) so the mutable borrow ends only afte _b goes out of scope?
The immutable reference you return is derived from the mutable reference; for the purposes of borrow checking, the mutable borrow cannot end before the immutable one does.
Besides which, the scope of the mutable reference begins and ends in main, not in A::m. Irrespective of the return type, a borrow passed into a function must survive that function.
I don’t know what to point you to, but there’s nothing special going on here. A method is just a function in a dress. If a function needs a borrow, that borrow has to be constructed before you call the function, you pass it to the function, and then it goes out of scope in the caller at some point in the future.
As for your second post, it works because you don’t store the result of a.m(); the mutable borrow can end immediately.
Nnnnnnnyesno. First of all, something goes "out of scope" at the end of the defining scope, nowhere else. When a borrow ends is a different concern.
Borrows either end immediately, or when they go out of scope. ...unless you're using a nightly compiler with non-linear lifetimes enabled, in which case the answer is "whenever the compiler believes it can get away with ending it (possibly before going out of scope), which might be when you want it to, or might not, who knows, it's still under development, have a cookie".
The point being that this is equal to the following pseudocode
fn main() {
let mut a = A {};
A::m(&'outer_scope mut ('outer_scope a)); // <- mutable borrow valid for local scope
// (a is attributed a lifetime for clarity)
// !! borrow is MOVED into the method!
// <- mutable borrow is dropped by method; a new borrow can be created
{
let b: &'inner_scope A = A::m(&'inner_scope mut ('outer_scope a));
// <- mutable borrow with a lifetime valid for scope of b!
// !! The borrow is moved into the method as well
// <- `&mut A` is re-borrowed as `&A`, which means `&mut A` is still 'alive'
// => WHERE 'outer_scope: 'inner_scope
} // <- Scope closed, so b is dropped. Transitively the `&mut A` borrow
// is dropped as well
}
Keep in mind that &mut T and &T are actual types holding a value [the pointer], which differ from T itself. When you create a borrow, it's moved to the places where you use it.
Working with &T types (AKA immutable borrows) is not difficult since they implement Copy. Immutable borrows can thus be freely used since the compiler will automatically call clone when necessary. &mut T do NOT implement Copy to prevent aliasing. This is why you need your previous mutable borrow to be dropped before you can create a new one.