Lifetimes
Let's try to complete the previous exercise by adding an implementation of IntoIterator
for &TicketStore
, for
maximum convenience in for
loops.
Let's start by filling in the most "obvious" parts of the implementation:
#![allow(unused)] fn main() { impl IntoIterator for &TicketStore { type Item = &Ticket; type IntoIter = // What goes here? fn into_iter(self) -> Self::IntoIter { self.tickets.iter() } } }
What should type IntoIter
be set to?
Intuitively, it should be the type returned by self.tickets.iter()
, i.e. the type returned by Vec::iter()
.
If you check the standard library documentation, you'll find that Vec::iter()
returns an std::slice::Iter
.
The definition of Iter
is:
#![allow(unused)] fn main() { pub struct Iter<'a, T> { /* fields omitted */ } }
'a
is a lifetime parameter.
Lifetime parameters
Lifetimes are labels used by the Rust compiler to keep track of how long a reference (either mutable or
immutable) is valid.
The lifetime of a reference is constrained by the scope of the value it refers to. Rust always makes sure, at compile-time,
that references are not used after the value they refer to has been dropped, to avoid dangling pointers and use-after-free bugs.
This should sound familiar: we've already seen these concepts in action when we discussed ownership and borrowing. Lifetimes are just a way to name how long a specific reference is valid.
Naming becomes important when you have multiple references and you need to clarify how they relate to each other.
Let's look at the signature of Vec::iter()
:
#![allow(unused)] fn main() { impl <T> Vec<T> { // Slightly simplified pub fn iter<'a>(&'a self) -> Iter<'a, T> { // [...] } } }
Vec::iter()
is generic over a lifetime parameter, named 'a
.
'a
is used to tie together the lifetime of the Vec
and the lifetime of the Iter
returned by iter()
.
In plain English: the Iter
returned by iter()
cannot outlive the Vec
reference (&self
) it was created from.
This is important because Vec::iter
, as we discussed, returns an iterator over references to the Vec
's elements.
If the Vec
is dropped, the references returned by the iterator would be invalid. Rust must make sure this doesn't happen,
and lifetimes are the tool it uses to enforce this rule.
Lifetime elision
Rust has a set of rules, called lifetime elision rules, that allow you to omit explicit lifetime annotations in many cases.
For example, Vec::iter
's definition looks like this in std
's source code:
#![allow(unused)] fn main() { impl <T> Vec<T> { pub fn iter(&self) -> Iter<'_, T> { // [...] } } }
No explicit lifetime parameter is present in the signature of Vec::iter()
.
Elision rules imply that the lifetime of the Iter
returned by iter()
is tied to the lifetime of the &self
reference.
You can think of '_
as a placeholder for the lifetime of the &self
reference.
See the References section for a link to the official documentation on lifetime elision.
In most cases, you can rely on the compiler telling you when you need to add explicit lifetime annotations.
References
Exercise
The exercise for this section is located in 06_ticket_management/06_lifetimes