This week I learned 28
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.