They can be elided in most useful cases. When they cannot be elided, it's because that would cause ambiguities. And requiring the author to disambiguate is a good compromise.
It is already the case that the compiler can infer a shorter lifetime than the liveness of the item. The trick is that it can only do so for shared borrows &T
(because they are covariant in T
). It is not true for mutable bows &mut T
(because they are invariant in T
). The reasoning for this rule is important: It enforces the Shared XOR Mutable principle that the language fundamentally depends upon.
Interior mutability is intended to work around limitations associated with this unbreakable rule. These types are based on UnsafeCell
and they allow safe mutation through shared references (with their own unique limitations), giving a "best of both worlds" escape hatch for when you really need it. The docs for the typed-arena
crate may introduce you to some compelling possibilities in this direction.
This might help soothe your discomfort. The lifetime annotations are just names for generic parameters. Like T
in Option<T>
. There is little difference: You have to type T
to name the parameter; you have to type 'a
to name the lifetime. The tick mark '
is an indicator that the "kind" of generic parameter you are dealing with is specifically a region for the borrow checker.
It might seem weird to give it a name in the first place. After all, the compiler is pretty smart, right? It turns out to be useful for distinguishing between types.
"What's the use of their having names," the Gnat said, "if they won't answer to them?"
"No use to THEM," said Alice; "but it's useful to the people that name them, I suppose. If not, why do things have names at all?"
~ Through the Looking-Glass and What Alice found there
Consider Result<T, E>
: It would be a much less useful type if it could only be Result<T, T>
. But that would hypothetically allow you to elide both parameters entirely because they will always be the same. We will see shortly that this used to be the case for lifetimes in prehistoric Rust, and it was not a good design.
I think a much better introduction is not any forum thread, but the initial motivation for including lifetimes in structs in the first place: Lifetime notation · baby steps [1]
Nico formulates the case better than I have (even though this uses a bikeshed syntax from 2012, the ideas that exist today remain consistent):
What is in fact happening here in terms of the formalism is that the
StringReader
type has a lifetime parameter namedself
. In other words,StringReader/&self
is a generic type, just likeOption<T>
, except that it is not generic over a typeT
but rather over the lifetimeself
. It is of course possible to have a type likeFoo/&self<T>
, which is generic over both a lifetimeself
and a typeT
.One thing which would sometimes be useful (but which is currently unsupported) is the ability to have more than one lifetime parameter on a struct. There are no theoretical reasons for this limitation, it’s simply that the syntax and defaults we’ve adopted didn’t seem to scale up to multiple parameters.
And to wrap up, a few more gems from the same source. These are good for historical context of the original motivation, but they may contain irrelevant and obsolete notions:
"Option 6" is practically the syntax that we ended up with,
s/:/'/
↩︎