This week I learned a bit more about lifetimes. The following program declares 3 structs: A, B, and C. It uses a single lifetime, 'a. It does not compile.

struct A;

struct B<'a> {
    a: &'a A,
}

impl<'a> B<'a> {
    fn call(&mut self) {}
}

struct C<'a> {
    b: &'a mut B<'a>,
}

impl<'a> C<'a> {
    fn call(&mut self) {}
}

fn main() {
    let mut a = A {};
    let mut b = B { a: &a };
    let mut c = C { b: &mut b };

    c.call();

    b.call();
}

cargo build fails with the following error.

% cargo build
error[E0499]: cannot borrow `b` as mutable more than once at a time
  --> src/main.rs:26:5
   |
22 |     let mut c = C { b: &mut b };
   |                        ------ first mutable borrow occurs here
...
26 |     b.call();
   |     ^
   |     |
   |     second mutable borrow occurs here
   |     first borrow later used here

error: aborting due to previous error; 1 warning emitted

For more information about this error, try `rustc --explain E0499`.

The problem is that C’s lifetime annotation is too strict. C has an exclusive reference to B for 'a. B has a shared reference to A for 'a.

To conform to C’s lifetime annotation, A must live at least as long as B. To conform to B’s lifetime annotation, B must live at least as long as C. Applying the transitive property: A must live at least as long as C. Going back to main, A has a lifetime of main, so C’s borrow of B is valid until the end of main.

main() {                          Lifetime of A
    let mut a = A {}; <--------------------------+
    let mut b = B { a: &a }                      | Lifetime of borrow
    let mut c = C { b: &mut b }; <---------------+---------------+
                                                 |               |
    c.call();                                    |               |
                                                 |               |
    b.call(); <- b is still exclusively borrowed |               |
} <----------------------------------------------+---------------+

Relaxing the annotations on this program allows it to compile.

struct A;

struct B<'a> {
    a: &'a A,
}

impl<'a> B<'a> {
    fn call(&mut self) {}
}

struct C<'a, 'b> {
    b: &'a mut B<'b>,
}

impl<'a, 'b> C<'a, 'b> {
    fn call(&mut self) {}
}

fn main() {
    let mut a = A {};
    let mut b = B { a: &a };
    let mut c = C { b: &mut b };

    c.call();

    b.call();
}

C’s lifetime constraints now allow B to be borrowed at 'a and A to be borrowed at 'b. Now C’s borrow of B is indirectly dependent on 'a. Now after c.call() the compiler can decide if the c is still live. Since it isn’t, it allows b to be borrowed mutably again for b.call().

This post is a bit scattered because I still don’t completely understand this.