First of all what are lifetimes? Lifetimes define the valid range of a reference, in other words lifetimes are a tool used by the compiler to compare the lifetime of the owner and the borrower, in order to avoid dangling pointer as much as possible.
let r;
claims a variable that becomes a reference to the x variable in the inner statement block, and when the inner statement block ends, the variable x (owner) is released out of scope and r becomes a dangling reference, so the compiler reports an error.
Borrowing checker
The compiler has a borrow checker to compare scopes and decide whether the reference is valid. The above diagram has two comments 'a
and 'b
to represent the scope of r, x respectively. The 'b
of the memory statement block is much smaller than the outer 'a
, and the compilation stage finds that x has a shorter life cycle than r, so an error prevents compilation.
If you want to fix it, it’s as simple as putting println
in the inner statement block. Most of the time, we don’t need to display the specified lifetimes, the compiler is smart enough to infer them for us automatically, but there are exceptions.
Look at an example
Let’s look at an example where we need to specify lifetimes, longest
returns the longest reference to a string and compiles with an error.
The compiler is baffled, he doesn’t know which reference is returned by the function and needs to specify the life cycle. And it was very kind to give a hint.
|
|
Here is a rule of lifetimes: If the function input parameters have references (all references must have lifetimes), if the return result is not a reference, then you can omit to mark the life cycle, and vice versa must be marked.
The reason is very simple, if the return result is a reference, then according to the three principles of ownership, the object he refers to must not be created inside the function, because the function returns, the reference to the object will be released, the return of the reference becomes a dangling reference.
Therefore, the life cycle of the returned reference must be consistent with the input parameters, which leads to the second rule of lifetimes: If the input parameters are only one reference with lifetimes, and the return value is also a reference, then the two life cycles must be consistent, you can omit to mark lifetimes, and vice versa must be marked.
Here a little around, you need to think carefully and run more test examples to deepen understanding, I just started to contact here also took a lot of detours.
Life cycle syntax
The syntax is nothing special, it is a generic syntax, usually starting from 'a
, 'b
, 'c
, and so on, or you can write it as something else.
The above example <'a>
is a generic syntax and the function signature indicates that the x, y life cycles are the same, so the return reference is naturally 'a
.
But the static lifecycle above uses the `‘static’’ keyword to indicate that the reference survives for the entire duration of the program. The string will be compiled into the binary data segment.
The above is an example combined with normal generic parameters, <'a, T>
, where the first 'a
is the lifecycle and the second is the generic T
, requiring the implementation of the Display
trait. Note that the order should not be reversed here, as an error will be reported if T
is placed before it.
|
|
Lifetime Annotations in Function Signatures
|
|
This example will report an error, even though we specified the life cycle, but it doesn’t help. string2
is released at the end of the block, and it is illegal to continue using the string2 reference when println resut. Just put println inside the block.
Multiple lifecycle parameters
|
|
If the function has more than one lifecycle parameter, 'a
, 'b
, and returns a reference to 'a
, the compiler will report an error.
The principle is simple: the compiler cannot determine the valid length of the two lifetimes and needs to specify a constraint.
The final function is shown above, where 'b: 'a
means that 'b
must include the 'a
life cycle length.
annotations within the structure
When a structure is defined, if a reference exists, it is also annotated with 'a
, and the generic syntax is not repeated here. part
is a string reference that exists before the instance i
is created, and is released at the same time as i
leaves scope.
If you remove the lifetime annotation from the generic, you will get an error.
Annotation of struct methods
The annotation syntax for struct methods is to write <'a>
after the impl
keyword, and use it after the struct name. This example is not annotated with announce_and_return_part
, which leads to another rule If the method has multiple input lifecycle parameters and one of the parameters is &self or &mut self, which means it is a method of an object, then all output lifecycle parameters are given to self’s life cycle
If you replace the announce_and_return_part
return value with announcement
you will get an error.
This requires a display specification to assist the compiler in completing the check and specifying lifetime. In addition, the life cycle is more complicated when subtyping, covariance, and inversion are involved, for those interested, see the nomicon official documentation.
Summary
I’ve written a bunch of miscellaneous things, but I think we should get started and practice more, and figure it out. When I first learned rust, I was told to add 'a'
to the life cycle if I couldn’t compile it, and to use clone
for data.
In fact, or to understand the essence, and Go GC runtime traversal is different, the inspector to determine when and where to release resources at compile time, you need to collect additional information. For example, a structure object has a reference field. If its lifecycle cannot be confirmed, should the field be released when the structure object is released? Or can the field be automatically released early, so does it result in a dangling reference? Obviously, this violates the security rules.