Summary
fn main() {
'a: { // block label declaration
let x = 4;
let r : &'a usize = &'a x; // explicit lifetime 'a
}
}
Motivation
Why are we doing this?
Lifetimes are complicated topic for novices to grasp. They are connected both to memory safety and to generics. I saw some tutorial (maybe past version of the Rust Book) using similar code, with "This is not an actual Rust code" disclaimer.
For usual generics T
can be assigned some concrete type, and mysterious Vec reified into easier Vec. Here the switch is illustrated:
fn main() {
let x = Vec::<u32>::new();
fn qqq(s: Vec<u32>) -> Vec<u32> { s }
let r2 = qqq(x);
}
to fn qqq<T>(s: Vec<T>) -> Vec<T> { s }
But for lifetimes there are no concrete types visible in code. You can't provide simple case
fn main() {
'a: {
let x = 5;
let r : &'a u32 = &'a x;
fn qqq(s: &'a u32) -> &'a u32 { s }
let r2 = qqq(r);
}
}
before explaining complicated one:
fn main() {
'a: {
let x = 5;
let r : &'a u32 = &'a x;
fn qqq<'y>(s: &'y u32) -> &'y u32 { s }
let r2 = qqq(r);
}
}
which reduces by elision and removal of extras to usual
fn main() {
{
let x = 5;
let r : &u32 = &x;
fn qqq(s: &u32) -> &u32 { s }
let r2 = qqq(r);
}
}
What use cases does it support?
Users manually annotating blocks of code with labels and assigning/asserting their reference lifetimes. Complier just ensures that user's guess are correct (or maybe narrows down if user specified narrower than automatically calculated lifetime).
What is the expected outcome?
Documentation and especially tutorials use explicit lifetimes as a easier illustrative prior to explaining generic lifetimes. Maybe documentation using fully specified, lifetimed reference from start before switching to simplified inferred ones.
The &
operator said to have kind ' -> T -> T
and with this change it can also be used with both a lifetime and a type, without being forced to rely on inference.
Detailed design
TODO
Drawbacks
- Possible confusion with generic lifetime parameters which are also have this style '-letter
- Possible confusion with labeled break
- As usual, complication of language, grammar and compiler
Alternatives
- Status quo
Unresolved questions
- Detailed design.
- Shall reference-taking operator also support lifetimes or only reference type?
- Will it be more or less useful after non-lexical borrows?
let r2 = qqq::<'a>(r);
?- Can compiler error message be improved by this?
'a: {
or just'a {
?- Is having loop label
'a: loop { ... }
also mean lifetime region a good idea or not? - Recommended style. Maybe it should be verbose
'SomethingLonger: { ... &'SomethingLonger ... }
, especially as the feature is aimed at documentation. - Attaching explicit lifetimes to lets instead of blocks
'a: let x = 5;