Before Rust 1.0, writing code that returns a reference without any lifetime annotation wouldnโt compile. Because Rust does not know whatโs the lifetime of the returned reference:
// ๐ซ would not compile
fn first_word(s: &str) -> &str { ... }
// โ
compile
fn first_word<'a>(s: &'a str) -> &'a str {
In later versions, the Rust compiler can automatically analyze and figure out the lifetime. It is called lifetime elision and is based on a set of rules. For now, these rules are applied for the fn and impl blocks. In the future, more elision rules will be added.
In a function, lifetimes on the parameters are called input lifetimes, and lifetimes on the return values are called output lifetimes.
If the lifetimes are not explicitly annotated, the compiler will try to apply these three rules:
Each parameter that is a reference gets its own lifetime parameter, for example:
fn foo(first: &str) // is equivalent to fn foo<'a>(first: &'a str) fn foo(first: &str, second: &Bar) // is equivalent to fn foo<'a, 'b>(first: &'a str, second: &'b Bar)If there is exactly one input lifetime parameter, that lifetime is assigned to all output lifetime parameters:
fn foo(first: &str) -> &str // is equivalent to fn foo<'a>(first: &'a str) -> &'a strIf there are multiple input lifetime parameters, but one of them is
&selfor&mut self, the lifetime ofselfwill be assigned to all output lifetime parameters:fn foo(&self, first: &str) -> &str // is equivalent to fn foo<'a, 'b>(&'a self, first: &'b str) -> &'a str
If all of the above rules are satisfied, you are good to go. If the compiler fails to apply any of the above rules, it will stop and show a compile error, asking you to annotate the lifetime yourself.
For example, in the following function, there are two lifetime parameters 'a and 'b:
// ๐ซ would not compile
fn split<'a, 'b>(source: &'a str, delimiter: &'b str) -> &??? str
In this case, the compiler could not figure out the output lifetime, so it will show a compile error.