The compiler complains about borrowing as mutable twice even though the return value is thrown away

I have the following code (simplified):

trait Trait1 {
    fn f(&mut self);
}

struct S1;
impl Trait1 for S1 {
    fn f(&mut self) { }
}

trait Trait2<'a, T: 'a + Trait1> {
    fn field(&'a mut self) -> &'a mut T;
    
    fn do_something(&'a mut self) {
        self.field().f();
        self.field().f();
    }
}

struct S2(S1);
impl<'a> Trait2<'a, S1> for S2 {
    fn field(&'a mut self) -> &'a mut S1 {
        &mut self.0
    }
}

The lifetimes are required because of another code, don't bother about them.

The important thing here is that the compiler complains:

error[E0499]: cannot borrow `*self` as mutable more than once at a time
  --> src/lib.rs:15:9
   |
10 | trait Trait2<'a, T: 'a + Trait1> {
   |              -- lifetime `'a` defined here
...
14 |         self.field().f();
   |         ------------
   |         |
   |         first mutable borrow occurs here
   |         argument requires that `*self` is borrowed for `'a`
15 |         self.field().f();
   |         ^^^^ second mutable borrow occurs here

But I don't understand: the compiler can see that even though Trait2.field() borrows self as mutable, it was finished executing. As I understand, the borrowing of self only affects the return value. Here it is not even used in the return value. Shouldn't the compiler accept that?

This issue comes up a lot when you have &'a mut self in your method. What happens is that by calling self.field() you are borrowing self for the duration of the 'a lifetime. That lifetime outlives the function, so it means you can't call self.field() again until after the function returns.

Incidentally, this will work if you had &'a self (note the lack of mutability) because of something called variance. This is where the compiler is allowed to shorten/lengthen a lifetime as it sees fit. Immutable references are covariant, so the compiler can shorten the lifetime of the returned &'a T until the point where the return value gets dropped. Mutable references (deliberately) can't be shortened, so the compiler doesn't have the wiggle room it needs to make your code compile.

Some solutions are...

  1. Throw more lifetimes at the situation (specify that some 'this lifetime outlives 'a, so the compiler knows you don't want to lock self for the duration of do_something(), or
  2. Restructure your code so you don't get into this situation

Congrats on running into a borrow checker error including variance, by the way. That's when you know you're really starting to get a hang on these "lifetime" things and are a more advanced Rustacean!

This is actually the interesting part, because stripping the presented code from all lifetime annotations actually makes the code compile:

trait Trait1 {
    fn f(&mut self);
}

struct S1;
impl Trait1 for S1 {
    fn f(&mut self) { }
}

trait Trait2<T: Trait1> {
    fn field(&mut self) -> &mut T;
    
    fn do_something(&mut self) {
        self.field().f();
        self.field().f();
    }
}

struct S2(S1);
impl Trait2<S1> for S2 {
    fn field(&mut self) -> &mut S1 {
        &mut self.0
    }
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
    Finished dev [unoptimized + debuginfo] target(s) in 0.31s

When it comes to lifetimes, the usage is also important. If I see a lifetime attached to &(mut) self, my instinct tells me something might be wrong with the calling code, instead. Having some example code that causes the compiler to complain about missing lifetime specification would be needed to investigate further.

See my adventure with lifetime inference problems:

In the end, it turned out that using for<'a> instead of attaching 'a to &(mut) self was the way to go about it. See also:
https://doc.rust-lang.org/nomicon/hrtb.html

I know that, and actually I solved the problem by changing them, but this is not what I asked,

Regarding the variance thing.. That doesn't sound quite right. Mutable references are covariant in the lifetime on the reference. You need nested references to hit it, i.e. &'a mut &'b mut T is covariant in 'a but invariant in 'b.

If it works with immutable references, it's more likely due to immutable borrows being allowed to overlap, rather than them being shortened.

3 Likes