The things that make the lifetime annotation syntax awkward for me are
- it’s not a real type because it can’t be instantiated like a real type, but it can be passed into the type parameter of a generic type like a real type, and it does have covariant inversion for real subtyping
- it can also be used as a type constraint like Trait, in addition to other lifetime annotations like
'a: 'b, it can also be constrained with normal types like
This setting is still acceptable. However, in addition, lifetime appears from time to time together with some other strange syntax, and it is still always scary at this point.
I want to overcome my fear of lifetime by 1. confronting it and 2. organizing and analyzing it.
After roughly sorting out a handful of code I don’t understand, I found that the syntax I don’t understand about lifetime seems to be mainly when it is put together with various generic parameters and even trait constraints.
Find a few examples to look at in detail.
Ref<‘a, T: ‘a>
This error is said to be reported (but I didn’t reproduce it locally, probably because the rust compiler has added a new omission rule).
Why do we need to add lifetime constraints to the generic parameters here?
Because T may be a structure containing a reference, such as
Lexer<'a>, which has the ownership of this Lexer but has external references inside, or it may be a reference type such as
&PlainText. In this case,
T:'a can be used to declare that the lifetime of the type T must be within the scope of
Cow<‘a, B: ‘a + ToOwned + ?Sized>
Look at another example of a Cow from the standard library.
Like the previous example, here we have
'a specified for B in the generic parameter to constrain its lifecycle to be within
The main thing in this code is the
?Sized trait, so let’s look it up.
Sized trait means that the compiler knows the length of the type, and all type arguments have a default
?Sized means that the bound is released, allowing non-fixed-length types to be accepted. The non-deterministic types are mainly from Slice and Trait Objects, such as
dyn MyTrait and
[u8]. Non-fixed-length types cannot be stored on the stack, e.g.
But references to non-fixed-length types are still fixed-length and can be passed on the stack, such as
&'a B ,
&dyn MyTrait ,
&[u8] . The reason B in
Cow<'a, B> allows
?Sized is because the contents of the Borrowed part of the enum are a reference to B. If
?Sized had not been declared,
Cow<> would not have been available for
box_disaplayable<‘a, T: Display + ‘a>
Why does this code not compile?
As in the previous example, the reason is that T could be a structure containing a reference, or it could be a trait that implements a reference type such as
impl Display for &MyType. When you move, you need to make sure that it is still in lifetime range after the move.
http://zderadicka.eu/higher-rank/ This example is better, I have a
ChuckSum trait wrapped in a
calc method with two algorithmic implementations, Xor and Add.
It makes sense. There are several different algorithms, all taking parameters that satisfy the
Read trait, to calculate checksum.
The checksum of a file is calculated according to the algorithm specified in the parameter.
This will report an error.
Follow the prompts to change it again.
Why does the error borrowed value does not live long enough?
Because according to the lifetime constraint, the lifetime of the argument to calc needs to be larger than
'a, but the lifetime of the variable buf is smaller than
'a, so the error is reported.
A reference type can be a type parameter of Generic Trait, which is fine in itself, but a reference requires a lifetime, and the lifetime annotation that has appeared in a function in the past can only come from the declaration of the lifetime generic parameter of the function. If you use a function’s lifetime ‘a to annotate references in generic parameters, you will get the above error.
Using a function’s lifetime ‘a to mark references in
Checksum<&[u8]> is not correct from a lifecycle perspective. The lifetime of a reference in
Checksum<&[u8]> is only relevant to the point at which it is called, and it will be different for two different calls.
It is possible to do a work around by defining a separate function and ensuring that the lifetime of the reference is consistent through the lifetime tag of the function, and there is no problem.
It would be ugly to wrap a separate function for each similar call point. For this reason rust introduced the HRTB (Higher Rank Trait Bound) syntax, which is this
for <'a>. Change it like this.
means that its lifetime is independent of the lifetime marking of the foo function, and is bound specifically at each specific call point, and there is no need to define a separate function just because of lifetime.
The lifetime marking of structs and functions is relatively easy to understand, but the possibility of substituting reference types for parameters of generic types is one area that is easy to forget. In addition, there is no way to foresee the lifetime of generic parameters when defining a generic structure or Trait, and it is easier to encounter accidents at this time.
Should I add lifetime constraints to generic parameters when defining a structure? The generalized parameters are easy to be mentally defined.
If it is related to a reference, you need to understand that even if the generic parameter is an owned type, there may still be a reference field in it, and it will be related to lifetime. lifetime of the generic parameter satisfies the lifetime constraint of the structure.
Are there any generic parameters that might be passed in by reference in the Trait being used?
It doesn’t make sense to constrain such arguments by the lifetime of the function where they are called; the lifetime they need must be smaller than the function’s lifetime ‘a, and they won’t satisfy the function’s lifetime constraint. In this case, the Trait type itself can be constrained by
for<'a>, and the function itself can be used without the lifetime argument.