Lifetimes
Every reference in Rust points at data that someone else owns, and the
compiler guarantees that the data is still there whenever the reference is
used. The ownership lesson showed the rules for how you may borrow (many
& or one &mut). This lesson is about how long: the span of code during
which a reference is valid is its lifetime.
Most of the time the compiler works lifetimes out on its own, and you never
write one. You need to spell them out when a function returns a reference and
the compiler can't tell which argument it borrows from, and when a struct
stores a reference. This lesson covers why lifetimes exist, the 'a syntax,
the rules that let you leave them out, structs holding & and &mut
references, and 'static.
References can't outlive their data
Here r borrows name, but name is dropped at the end of the inner block,
before r is used:
fn main() {
let r;
{
let name = String::from("Ferris");
r = &name;
}
println!("{r}");
}In a language without this check, r would point at freed memory, a
dangling reference. Rust's borrow checker compares two spans: how long
name lives (until the inner }) and how long the borrow r is used (until
the println!). The borrow has to fit inside the lifetime of the data, and
here it doesn't. Moving the println! into the inner block, or moving name
out of it, fixes it.
Inside one function the compiler sees everything, so it never needs help. The interesting cases are at function boundaries.
Lifetimes in function signatures
A function that takes one reference and returns one needs no annotations:
fn first_word(text: &str) -> &str {
match text.split_once(' ') {
Some((word, _)) => word,
None => text,
}
}
fn main() {
let sentence = String::from("borrow checker");
let word = first_word(&sentence);
println!("{word}");
}borrowThe returned &str can only come from text, so the compiler knows word
borrows from sentence and must not outlive it.
With two reference parameters that is no longer obvious. This function looks up a key in a map of settings and falls back to a default:
use std::collections::HashMap;
fn setting(map: &HashMap<String, String>, key: &str, fallback: &str) -> &str {
match map.get(key) {
Some(value) => value,
None => fallback,
}
}
fn main() {
let map = HashMap::new();
println!("{}", setting(&map, "theme", "dark"));
}The compiler checks each function on its own, using only its signature, not
its body. From the signature alone it can't tell whether the result borrows
from map, key or fallback, and callers need to know that to check their
own code. You tell it with lifetime parameters:
use std::collections::HashMap;
fn setting<'a>(map: &'a HashMap<String, String>, key: &str, fallback: &'a str) -> &'a str {
match map.get(key) {
Some(value) => value,
None => fallback,
}
}
fn main() {
let mut map = HashMap::new();
map.insert(String::from("theme"), String::from("light"));
println!("{}", setting(&map, "theme", "dark"));
println!("{}", setting(&map, "font", "monospace"));
}light
monospaceA lifetime parameter is a name starting with an apostrophe, usually 'a,
'b and so on. It is declared in angle brackets after the function name,
like a generic type, and written after the & of a reference: &'a str,
&'a mut Vec<u32>.
The signature now says: the returned &str lives at most as long as both the
map borrow and the fallback borrow, because it may come from either. The
compiler's suggestion put 'a on key as well, which would compile, but the
result never borrows from key, so leaving it out is more accurate: the key
can be a temporary that is gone as soon as the call returns.
What an annotation means
Lifetime annotations don't change how long anything lives. They describe a relationship between references, and the compiler then checks both sides of it:
- Inside
setting, the body may only return something that lives for'a. Returningkeywould be an error, becausekeyhas no'a. - At every call,
'abecomes the shorter of the two borrows passed in, and the result may not be used after that.
The second point means the caller's code is checked against the signature:
use std::collections::HashMap;
fn setting<'a>(map: &'a HashMap<String, String>, key: &str, fallback: &'a str) -> &'a str {
match map.get(key) {
Some(value) => value,
None => fallback,
}
}
fn main() {
let map = HashMap::new();
let chosen;
{
let fallback = String::from("dark");
chosen = setting(&map, "theme", &fallback);
}
println!("{chosen}");
}The map is empty, so if this ran, chosen would point at fallback, which
is gone by the println!. But the compiler doesn't look at what the map
holds: it rejects the call because the signature says the result may
borrow from fallback, and that would be rejected even if theme were in the
map. That is the trade-off of checking by signature. In return, changing a
function's body can never break the code that calls it, as long as the
signature stays the same.
Lifetime elision
Writing 'a on every reference would be tedious, so the compiler fills in
lifetimes following three elision rules. If the rules leave no doubt about
the output lifetime, you don't write any:
- Each reference parameter gets its own lifetime:
fn f(a: &str, b: &str)meansfn f<'a, 'b>(a: &'a str, b: &'b str). - If there is exactly one input lifetime, every output reference gets it:
fn first_word(text: &str) -> &strmeansfn first_word<'a>(text: &'a str) -> &'a str. - If one of the parameters is
&selfor&mut self, output references get the lifetime ofself.
setting needed annotations because rule 1 gave it three input lifetimes and
it has no self, so neither rule 2 nor rule 3 applies.
Rule 3 is why methods rarely need annotations. A method that takes &self
and another reference returns something borrowed from self by default:
struct Library {
titles: Vec<String>,
}
impl Library {
fn find(&self, prefix: &str) -> Option<&str> {
for title in &self.titles {
if title.starts_with(prefix) {
return Some(title);
}
}
None
}
}
fn main() {
let library = Library {
titles: vec![String::from("Dune"), String::from("Emma")],
};
let found = {
let query = String::from("Em");
library.find(&query)
};
println!("{found:?}");
}Some("Emma")query is dropped before found is printed, and that's fine: by rule 3 the
result borrows from library, not from prefix. If a method returns
something borrowed from another parameter instead, rule 3 gets it wrong and
you annotate that parameter and the return type yourself.
Structs that hold references
A struct field can be a reference, but then the struct is only valid while the data it points at is. The struct declares a lifetime parameter and uses it on the field:
#[derive(Debug)]
struct Record<'a> {
name: &'a str,
city: &'a str,
}
fn parse_record(line: &str) -> Option<Record<'_>> {
let (name, city) = line.split_once(',')?;
Some(Record {
name: name.trim(),
city: city.trim(),
})
}
fn main() {
let csv = String::from("Ada, London\nGrace, New York\nbroken line");
let mut records = Vec::new();
for line in csv.lines() {
match parse_record(line) {
Some(record) => records.push(record),
None => println!("skipped: {line}"),
}
}
println!("{records:?}");
println!("{} lives in {}", records[1].name, records[1].city);
}skipped: broken line
[Record { name: "Ada", city: "London" }, Record { name: "Grace", city: "New York" }]
Grace lives in New YorkRecord<'a> reads "a record that borrows from something that lives for
'a". The records don't copy any text: name and city are slices of csv,
so parsing allocates nothing but the Vec. The price is that no Record can
outlive csv, and the compiler enforces it.
Record<'_> in the return type of parse_record is the elided form: the
struct has a lifetime, and rule 2 fills it in with the lifetime of line.
Writing '_ instead of plain Record makes it visible that the result
borrows.
Methods on a struct with a lifetime
An impl block for such a struct declares the lifetime the same way it
declares a generic type: impl<'a> Record<'a>. Methods can then return
references with either lifetime, and the difference matters:
struct Record<'a> {
name: &'a str,
city: &'a str,
}
impl<'a> Record<'a> {
fn name(&self) -> &str {
self.name
}
fn city(&self) -> &'a str {
self.city
}
}
fn main() {
let line = String::from("Ada,London");
let (name, city) = line.split_once(',').unwrap();
let first_name;
let city_name;
{
let record = Record { name, city };
first_name = record.name().to_string();
city_name = record.city();
}
println!("{first_name} / {city_name}");
}Ada / Londonname follows rule 3, so its result borrows from the Record itself and
can't be used once record is dropped; the example copies it into a String
instead. city returns &'a str, a borrow of the original line, so
city_name stays valid after record is gone. Use the struct's lifetime
when you hand back data the struct only borrowed.
Structs that hold mutable references
A struct can hold a &mut reference too, when it needs to change data it
doesn't own. The borrow rules still apply: while the struct is in use, it has
the only access to that data.
struct Tally<'a> {
counts: &'a mut Vec<u32>,
}
impl Tally<'_> {
fn record(&mut self, bucket: usize) {
if bucket >= self.counts.len() {
self.counts.resize(bucket + 1, 0);
}
self.counts[bucket] += 1;
}
fn total(&self) -> u32 {
self.counts.iter().sum()
}
}
fn main() {
let mut counts = vec![0, 0];
let mut tally = Tally {
counts: &mut counts,
};
tally.record(0);
tally.record(3);
tally.record(3);
println!("total: {}", tally.total());
counts.push(10);
println!("{counts:?}");
}total: 3
[1, 0, 0, 2, 10]impl Tally<'_> is the short form when no method needs to name the lifetime.
record takes &mut self because it changes the data behind the field, and
total only reads it.
main uses counts again right after the last use of tally. There is no
inner block: a borrow lasts until the last place it is used, not until the end
of the scope, so the &mut counts held by tally ends after total(). Using
counts while tally still has uses ahead is an error, shown in the mistakes
below.
'static
One lifetime has a name of its own: 'static, "valid for the whole run of the
program". String literals are &'static str, because their text is stored in
the program's binary:
fn weekday_name(day: u8) -> &'static str {
match day {
1 => "Monday",
2 => "Tuesday",
3 => "Wednesday",
_ => "some other day",
}
}
fn main() {
let name;
{
let day = 2;
name = weekday_name(day);
}
println!("{name}");
}Tuesdayweekday_name has no reference parameters to borrow from, so it could only
ever return something 'static, and the result can be kept as long as you
like.
When a lifetime error is hard to fix, the compiler sometimes suggests adding
'static. Only take that suggestion if the data really lives forever, like a
literal or a constant. Usually the right fix is different: return an owned
String instead of a &str, or keep the owner alive longer. You'll also see
'static as a bound (T: 'static) when values are sent to other threads,
which the concurrency lesson covers.
Common mistakes
Returning a reference to a local variable
fn greeting(name: &str) -> &str {
let message = format!("Hello, {name}!");
&message
}
fn main() {
println!("{}", greeting("Ferris"));
}message is dropped when greeting returns, so no lifetime annotation can
make this work. The function created the String, so it should hand over
ownership: return String and write message instead of &message.
A reference field without a lifetime
struct Record {
name: &str,
}
fn main() {
let record = Record { name: "Ada" };
println!("{}", record.name);
}Elision never applies to struct fields. Declare the lifetime on the struct and
use it on the field, struct Record<'a> { name: &'a str }, or store an owned
String if the struct should own its text.
Returning a reference with a lifetime the signature doesn't promise
use std::collections::HashMap;
fn setting<'a>(map: &'a HashMap<String, String>, key: &str, fallback: &str) -> &'a str {
match map.get(key) {
Some(value) => value,
None => fallback,
}
}
fn main() {
let map = HashMap::new();
println!("{}", setting(&map, "theme", "dark"));
}The return type promises a borrow that lives for 'a, but fallback was
only borrowed for some unrelated, possibly shorter, time. Every reference the
function might return needs the output's lifetime: write fallback: &'a str.
Using the data while a struct holds a mutable reference to it
struct Tally<'a> {
counts: &'a mut Vec<u32>,
}
impl Tally<'_> {
fn record(&mut self, bucket: usize) {
self.counts[bucket] += 1;
}
}
fn main() {
let mut counts = vec![0, 0];
let mut tally = Tally {
counts: &mut counts,
};
tally.record(0);
println!("{counts:?}");
tally.record(1);
}tally holds &mut counts until its last use on the final line, so
counts can't be read in between. Finish with the struct before touching the
data again, or read it through the struct (add a method on Tally that
returns what you need).
Summary
- A lifetime is how long a reference is valid. A reference may never outlive the data it points at, and the borrow checker proves it at compile time.
- Lifetime annotations (
'a) don't change how long anything lives. They tell the compiler which inputs a returned reference borrows from, so both the function and its callers can be checked from the signature alone. - Thanks to the elision rules you only annotate when they leave the output
ambiguous: several reference inputs and no
&self. - A struct holding a reference needs a lifetime parameter
(
struct Record<'a>) and can't outlive what it borrows. Methods that hand back borrowed data can return&'a Tso it outlives the struct. - A struct holding
&mutdata has exclusive access to it until the struct's last use. 'staticmeans valid for the whole program, as string literals are. Don't reach for it to silence an error; return owned data instead.
Practise what you read
Challenges that use the ideas from this lesson.