How to avoid having to write lifetime specifiers for some kinds of free functions and methods, because such specifiers can be inferred
Why lifetime specifiers are also needed for structs, tuple-structs, and enums containing references
How to write lifetime specifiers for structs, tuple-structs, and enums
Lifetime Elision
In the previous chapter we saw that every function signature must specify, for each returned reference, whether that reference has a static lifetime or otherwise to which function arguments its lifetime is associated.
This is because in the arguments there is just one reference, so it must be the one whose referred object is borrowed by the return value.
In this case there are several references in the return value, but still only one reference in the arguments.
Here, the return value contains three references: the first one has unspecified lifetime, the second one has the 'a lifetime, and the third one (that is the fourth field of the tuple), has the 'static lifetime. However, there is still only one reference in the arguments, so the first returned reference has an implied 'a lifetime.
Such allowed omission of the lifetime specifier is named lifetime elision . To simplify the syntax, the lifetime specifier can be elided when there is only one possible nonstatic value, and that happens when there is exactly one reference among the function arguments.
Lifetime Elision with Object-Oriented Programming
Reference Contained in the Return Value | Borrowed Argument |
---|---|
First field, having type &u8 | &self |
Second field, having type &f64 | &self |
Fourth field, having type &Vec<String> | &self |
Here, the object referred to by the second field of the returned tuple must live no longer than the object referred to by y, while the objects referred to by the first and fourth fields must live no longer than the object referred to by &self.
Reference Contained in the Return Value | Borrowed Argument |
---|---|
First field, having type &u8 | &self |
Second field, having type &f64 | y: &u8 |
Fourth field, having type &Vec<String> | &self |
Of course, the same rule also applies also for a &mut self argument .
The Need for Lifetime Specifiers in Structs Declarations
because, although y holds a reference to x, it lives less than x.
because y holds a reference to x, but it lives longer than x.
We also saw that function signatures have to be suitably annotated to perform the lifetime check of borrowings, considering only one function body at a time.
A similar issue occurs when a struct contains some references.
The latter code is illegal because y, through its ri field, borrows x, but it lives longer than x.
This application code is invalid, because, by invoking create_s, a reference to x gets stored inside the y object, and so y borrows x, but y lives longer than x.
This program can be compiled, and it will print: true 1.
Notice that, in the declaration of the S struct, the ri field is annotated by the 'static lifetime. In this code, the create_s function uses the ri argument just to decide how to initialize the ri field of the structure to create and return. The value of such argument is not stored in the structure. In any case, the ri field will surely contain a reference to a static value, which can be ZERO or ONE, and such value will never be destroyed.
This create_s function has the same signature as that of the previous example; but the previous example was invalid, as the argument was stored in a field of the struct, while this example is valid, as the argument is discarded after having been used.
So, without lifetime specifiers, the application programmer would be forced to read the body of the create_s library function to know if that function stores the reference it gets as an argument into the returned object or not. And this is bad.
Therefore, there is a need for further lifetime annotations, to allow both the application programmer and the compiler to focus on one function at a time, avoiding having to analyze the body of the create_s function to discover if the lifetimes of the objects used in the main function are correct.
So, even structs, similarly to functions, must explicitly specify the lifetimes of every reference contained in their fields.
This explains why even the former, apparently valid snippet, in fact generates the missing lifetime specifier compilation error.
Possible Lifetime Specifiers for Structs
Such field is allowed to refer only to static objects.
Such field is allowed to refer to static objects or to objects that, albeit nonstatic, preexist the whole struct object and live longer than it.
Such a struct actually contains a reference, but it is a static reference that cannot be assigned the value of any borrowed reference. So, there is never a lifetime issue in such a case, provided only static references are assigned to the ri field.
the usual `x` does not live long enough error would appear.
To see that x could be stored inside the struct, it is not required to examine the body of the create_s function or the field list of the S struct; it is enough to examine the signature of the create_s function, and the signature of S, which is the portion of its declaration before the open brace.
By examining the signature of the create_s function, it appears that it gets a reference as argument; a value of S type is returned; and that argument and return value have the same lifetime specifier, 'b. That means that the returned struct must live less than the borrowed i32 object.
By examining the signature of the S struct, it appears that it is parameterized by a lifetime specifier, and that means that some of its fields are nonstatic references.
So, we found that the create_s function gets a reference as an argument and returns an object parameterized by the same lifetime specifier. This implies that such object could borrow the object referenced by the argument, by storing into itself that object.
The compiler must separately check the consistency of the struct declaration and the consistency of the functions using that struct type. The clause struct S<'a> means that S borrows some objects, and the clause ri: &'a i32 inside the struct body means that the ri field is a reference that borrows an object.
Therefore, each reference field in a struct can have only two legal syntaxes: field: &'static type or field: &'lifetime type, where lifetime is also a parameter of the struct itself. If there are no reference fields or only static reference fields, the struct can have no lifetime parameters.
The first four statements are illegal. The declarations of _S1 and _S2 are illegal because the _f field is a reference field with no lifetime specifier. The declaration of _S3 is illegal because the 'a lifetime specifier is not declared as a parameter of the struct; and the declaration of _S4 is illegal because the parameter 'a is never used inside the body of the struct.
However, the last two struct declarations are valid. _S5 contains a reference to static objects, while _S6 contains a reference to an object that anyway must live longer than the struct itself.
Lifetime Specifiers for Tuple-Structs and Enums
This code is valid, but if any lifetime specifier is removed, the usual missing lifetime specifier error is generated.
By the way, notice the definition of the E::_D field. That is a reference to a static string slice. But we’ve seen such things since the beginning of this book; they are the string literals.
Lifetime Specifiers for Mutable References
It will print: 13. A reference to the byte is passed to f, which increments it and then returns back a reference to it. It is unusual, because when a mutable argument is passed to a function, usually there is no need to return a reference that borrows it. It is enough to reuse the passed reference.