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.