Lifetimes
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.
Example:
The program will not compile, because we’re trying to print reference r
, which
points to x
that already went out of scope (its lifetime 'b
is over).
Lifetime Elision
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
&self
ormut &self
(so, it’s a method), the lifetime ofself
is 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
Lifetime annotations describe the relationships of the lifetimes of multiple references to each other without affecting the lifetimes.
Functions
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 x
or 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 x
or 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
lifetime 'a
. Additionally, the string slice returned from the function will
live at least as long as lifetime 'a
.
When we pass concrete references to longest
, the concrete lifetime that is
substituted for 'a
is the part of the scope of x
that overlaps with the
scope of y
. In other words, the generic lifetime 'a
will get the concrete
lifetime that is equal to the smaller of the lifetimes of x
and y
.
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 x
and y
.
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
using 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 string2
, result
cannot be used at this point. The code fails to compile.
Structs
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 part
field.
Struct Methods
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 (&self
).
Static Lifetime
There is a special lifetime - 'static
- which marks a value pointed by a
reference to be “alive” during the entire execution of the program.