(Emphasis added.)
Once an inferred lifetime is calculated (via static analysis), or dictated (e.g. by a lifetime parameter), it doesn't change. They're not dynamic. However, they can seem to be that way for a variety of reasons:
- Lifetimes can be flow sensitive (not relevant for the rest of this post)
- Variance can allow reborrows or subtype coercions so that you have a different lifetime when, say, passing to a function or taking a subborrow.
- And reborrows are automatic in some cases
But they don't actually change, even though we usually call this subtyping "shrinking the lifetime". When it comes to type declarations and argument signatures, lifetimes take on single "values" -- regions of validity. They don't mean "this lifetime or any shorter one". In particular, if you have multiple lifetimes that all use the same name (&'a mut Test<'a>
, both references in Data<'a>
), the lifetimes refer to the same regions. You have asked for lifetime equality by giving them the same name.
Another key thing to know is that lifetimes behind a &mut
are invariant -- they cannot grow or shrink -- otherwise you could create memory unsafetly. That is, in
&'x mut Test<'y>
You can reborrow a shorter &'z mut Test<'y>
, but you can't reborrow or otherwise get a different lifetime in the Test<'_>
. Using any interaction through the &mut
, it will be a Test<'y>
.
So here:
fn main(){
let i = 0;
let mut m = Test { v: &i }; // Call this 'i, it has to be valid from here...
m.borrow_mut(); // #1 |
let i = m.v; // #2 |__ to here, where you use it again
}
m
is a Test<'i>
and 'i
has to cover #1 and #2. And when you call borrow_mut
, it takes a &'a mut Test<'a>
, and so
- The lifetimes must be the same
- It must be exactly
'i
because of the invariance property
- Thus you're taking a
&'i mut Test<'i>
This means your exclusive borrow lasts through #1 and #2 as well. So m
is still exclusively borrowed at #2 and you can't use it -- hence the error. This is why &'a mut Anything<'a>
is an antipattern -- you can never use the Anything<'a>
again. You can only use the &mut
or things derived from it.
Lifetimes are also forward-looking: if you have a pre-existing lifetime 'x
, and in some later code you create another borrow 'x
, the second borrow doesn't have to extend backwards in "time". It just needs to be valid from the point of borrowing forward for the rest of 'x
. So this example is pretty uninteresting on the surface:
fn main() {
let i = 0;
let ri = &i; // 'i
{ // |
let f = 1.0; // |
let rf = &f; // | 'f
let d = Data { ri, rf }; // |__|___ both can end here (last use)
}
}
The two borrows have to be the same, but they can both just be 'i
here. It is not the case that 'i
has to last for the entire scope of the ri
variable -- i.e. past the inner block. Rust used to work like that years ago, but now we have something called non-lexical lifetimes (NLL). Now borrows only have to last through the last use of the borrow, so 'i
can end just after creating d
.
However, we can make the example more interesting by forcing 'i
to last beyond the inner block:
fn main() {
let i = 0;
let ri = &i; // 'i
{ // |
let f = 1.0; // |
let rf = &f; // | 'f
let d = Data { ri, rf }; // | |___ 'f ends here (f is dropped)
} // |
println!("{ri}"); // |__ 'i has to last until here
}
Why does this still compile? d
can have type Data<'f, 'f>
, due to subtyping. The lifetime on &i
is covariant, so it can be "shrunk" (when you put a copy of it into d
, the copy's lifetime is coerced to the shorter 'f
).
This would work even with mutable borrows; you would get an implicit reborrow when creating d
instead of a copy.