I was reading the lifetimes chapter of the Rust book, and I came across this example for a named/explicit lifetime:
struct Foo<'a> {
x: &'a i32,
}
fn main() {
let x; // -+ x goes into scope
// |
{ // |
let y = &5; // ---+ y goes into scope
let f = Foo { x: y }; // ---+ f goes into scope
x = &f.x; // | | error here
} // ---+ f and y go out of scope
// |
println!("{}", x); // |
} // -+ x goes out of scope
It's quite clear to me that the error being prevented by the compiler is the use-after-free of the reference assigned to x
: after the inner scope is done, f
and therefore &f.x
become invalid, and should not have been assigned to x
.
My issue is that the problem could have easily been analyzed away without using the explicit 'a
lifetime, for instance by inferring an illegal assignment of a reference to a wider scope (x = &f.x;
).
In which cases are explicit lifetimes actually needed to prevent use-after-free (or some other class?) errors?
If a function receives two references as arguments and returns a reference, then the implementation of the function might sometimes return the first reference and sometimes the second one. It is impossible to predict which reference will be returned for a given call. In this case, it is impossible to infer a lifetime for the returned reference, since each argument reference may refer to a different variable binding with a different lifetime. Explicit lifetimes help to avoid or clarify such a situation.
Likewise, if a structure holds two references (as two member fields) then a member function of the structure may sometimes return the first reference and sometimes the second one. Again explicit lifetimes prevent such ambiguities.
In a few simple situations, there is lifetime elision where the compiler can infer lifetimes.
I've found another great explanation here: http://doc.rust-lang.org/0.12.0/guide-lifetimes.html#returning-references.
Note that there are no explicit lifetimes in that piece of code, except the structure definition. The compiler is perfectly able to infer lifetimes in
main()
.In type definitions, however, explicit lifetimes are unavoidable. For example, there is an ambiguity here:
Should these be different lifetimes or should they be the same? It does matter from the usage perspective,
struct RefPair<'a, 'b>(&'a u32, &'b u32)
is very different fromstruct RefPair<'a>(&'a u32, &'a u32)
.Now, for simple cases, like the one you provided, the compiler could theoretically elide lifetimes like it does in other places, but such cases are very limited and do not worth extra complexity in the compiler, and this gain in clarity would be at the very least questionable.