References have their lifetimes, which define a scope for when a reference is valid. Lifetimes can be implicitly infered but sometimes we need to specify them explicitly. The main aim of lifetimes is to prevent dangling references.
The program will not compile, because we’re trying to print reference
x that already went out of scope (its lifetime
'b is over).
Not all usages of references require explicit lifetime annotations. There are some situations where the compiler is able to infer the proper lifetimes on its own.
There are three rules of how compiler infers lifetimes. They apply to functions and methods:
- Each parameter that is a reference gets its own lifetime parameter.
- If there is exactly one input parameter, its lifetime is assigned to all output parameters (that’s one functions with one parameter don’t need lifetime specifications).
- If there are multiple input parameters, but one of them is
mut &self(so, it’s a method), the lifetime of
selfis alligned to all output parameters.
If after applying these rules there are still some references left without lifetime information, compilation will fail and explicit lifetime information will be required.
Lifetime annotations describe the relationships of the lifetimes of multiple references to each other without affecting the lifetimes.
Lifetime annotations in functions have meaning only when multiple types have annotations. Then the compiler can understand if some bindings have the same or different lifetimes.
Here’s a function that will not compile due to lack of lifetime annotations:
The compiler needs to know how the returned reference is related to the parameters of the function.
Here’s a valid version:
Potentially, the compiler could infer the lifetime knowledge from the code
itself - since I’m returning either
y, probably the lifetime of the
result should be the smallest common lifetime of these. However, that would bind
the lifetime of the result with that particular implementation! If I, as an
author of that function, change this implementation one day to always return
x, the lifetime infered by the compiler will change! It could than break some
code of clients of this function. That’s why Rust requires us to specify the
lifetime information explicitly.
Since the function can return either
y reference, the lifetime of both
the parameters and the return value will be the same -
'a. The function
signature now tells Rust that for some lifetime
'a, the function takes two
parameters, both of which are string slices that live at least as long as
'a. Additionally, the string slice returned from the function will
live at least as long as lifetime
When we pass concrete references to
longest, the concrete lifetime that is
'a is the part of the scope of
x that overlaps with the
y. In other words, the generic lifetime
'a will get the concrete
lifetime that is equal to the smaller of the lifetimes of
Because we’ve annotated the returned reference with the same lifetime parameter
'a, the returned reference will also be valid for the length of the smaller of
the lifetimes of
Using a function with lifetimes defined
Here’re examples of how lifetimes help in enforcing proper usage of references in terms of scope:
In the code above,
string2 has shorter lifetime than
string1. It means that
the lifetime of
result will end at the same time as
string2‘s. Since we’re
result when the lifetime is still valid, the code compiles and runs.
In the code above, the situation is similar, however, we’re trying to use (print)
result outside of the inner scope, when the lifetime of
string2 is already
over. Since the lifetime of
result is the same as that of
cannot be used at this point. The code fails to compile.
If a struct contains references as fields, they all need to have lifetime annotations.
This annotation means an instance of
ImportantExcerpt can’t outlive the
reference it holds in its
Thanks to the elision rules we don’t have to annotate the
method explicitly. The lifetime of the output
&str is the same as the lifetime
of the object (
There is a special lifetime -
'static - which marks a value pointed by a
reference to be “alive” during the entire execution of the program.