Reputation: 1821
I have read up on lifetimes and understood that every single variable binding has a lifetime. It seems as though, however, that I cannot think of a time you would actually need to use them, considering the compiler does quite a great worl at inferring them when necessary.
The Rust book, I have read. I would like an example that is simple to understand, even for someone like myself!
Upvotes: 8
Views: 3762
Reputation: 65682
We use lifetime parameters in Rust when a variable (that has some lifetime) refers to another variable with a different lifetime.
Let's consider these two statements:
let i = 42;
let ref_i = &i;
Here, i
has some lifetime, and ref_i
has some other lifetime. However, the type of ref_i
encodes the lifetime of i
(or some approximation of it that is sound); the type of a borrowed pointer is written &'a T
, and 'a
is the lifetime of the pointer's referent.
Borrowed pointers can only refer to a value that has a longer lifetime than the pointer's lifetime. If that was not the case, then the pointer would eventually be dangling, i.e. it would refer to a value that no longer exists. The compiler automatically validates this for you (so long as you don't write unsafe
code); that's something that other systems programming languages like C++ don't do. But in order to validate this, the compiler must know the lifetime of the value that the pointer refers to; that's why we have lifetime parameters in Rust. Fortunately, the compiler can also infer lifetimes in many situations, so it's transparent in these situations.
By design, Rust will only do local type inference. When compiling a function, the compiler doesn't inspect the body of other functions or other types to verify that the first function is correct; it only looks at their signature. For functions, we have elision rules that dictate when we can omit explicit lifetime parameters and what the compiler will infer them to be. For structs, we always have to mention them explicitly, because we almost always need to correlate the lifetime parameter on the struct with some other item (e.g. the lifetime parameter of a trait in a trait impl, or the return type on a method), and due to the compiler only doing local type inference, we need to encode this correlation explicitly in the signature.
Here's a simple example of a struct that contains a borrow:
struct Wrapper<'a>(&'a str);
impl<'a> Wrapper<'a> {
fn extract(self) -> &'a str {
self.0
}
}
First, on the struct definition, we need to introduce a lifetime parameter for the string slice. Then, we need to parameterize the impl
because Wrapper
expects a lifetime parameter (the 'a
in impl<'a>
defines the lifetime parameter, the 'a
in Wrapper<'a>
uses the lifetime parameter). On extract
, we can refer to the 'a
lifetime parameter defined on the impl
so that the function's return type matches the actual type of self.0
.
Upvotes: 12