Inaccurate compiler message about mutable references


#1

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

Any idea?


#2

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.


#3

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?


#4

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.


#5

Actually the scope ends at the end of a code block.
So the following should work:

struct A;

impl A {
    fn m(&mut self) -> &Self {
        self
    }
}

fn main() {
    let mut a = A {};
    {
        let _b = a.m();
    }
    a.m();
}

#6

That makes sense. Can you point me to the documentation regarding borrow on method call? Can’t find a useful one in those introductory tutorials.


#7

Why does the following work if borrow one goes out of the scope at the end?

fn main() {
    let mut a = A {};
    a.m(); // borrow one
    let b = a.m(); // borrow two
}

#8

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.


#9

Thanks. So the rule of thumb is that the borrow goes out of scope at the earliest possible point? I find this relevant.


#10

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”.


#11

Hmm. Maybe define “end immediately”?


#12

You posted an example of this exact behaviour: immediately after the method call (where you didn’t store the result), the borrow ended.


#13

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.