Advanced Lifetimes
Prerequisites: Advanced traits
Lifetime annotations never change how long a value lives. They describe relationships between borrows so the compiler can check them. This lesson makes those relationships precise.
The elision rules, exactly
You rarely write lifetimes because the compiler applies three rules to function signatures:
- Every elided lifetime in an input gets its own distinct parameter.
fn f(a: &str, b: &str)meansfn f<'a, 'b>(a: &'a str, b: &'b str). - If there is exactly one input lifetime, it is assigned to every elided
output lifetime.
fn trim(s: &str) -> &strties the result tos. - If one input is
&selfor&mut self, its lifetime is assigned to every elided output lifetime.
If none of these settles an output lifetime, you must write it:
fn pick(a: &str, b: &str) -> &str {
if a.len() > b.len() { a } else { b }
}
error[E0106]: missing lifetime specifier
The fix states the relationship: fn pick<'a>(a: &'a str, b: &'a str) -> &'a str.
Multiple lifetimes
A single 'a everywhere is the easy answer but can be too strict. If the
result only ever borrows from one argument, say so, and callers get more
freedom with the others:
fn find_after<'t>(text: &'t str, marker: &str) -> Option<&'t str> {
text.find(marker).map(|i| &text[i + marker.len()..])
}
fn main() {
let text = String::from("user=alice");
let value = {
let marker = String::from("user="); // dropped at the end of this block
find_after(&text, &marker)
};
println!("{value:?}");
}
Expected output:
Some("alice")
marker gets its own elided lifetime (rule 1), and the output is tied only to
't, so value may outlive marker.
Lifetimes on structs and impl blocks
A struct holding a reference needs a lifetime parameter, and its impl
block declares it again. The interesting choice is what methods return:
struct Parser<'a> {
input: &'a str,
pos: usize,
}
impl<'a> Parser<'a> {
fn new(input: &'a str) -> Self {
Parser { input, pos: 0 }
}
// Returns &'a str: borrowed from the input, NOT from the parser.
fn next_word(&mut self) -> Option<&'a str> {
let rest = &self.input[self.pos..];
let trimmed = rest.trim_start();
if trimmed.is_empty() {
return None;
}
let start = self.pos + (rest.len() - trimmed.len());
let end = trimmed.find(' ').map_or(self.input.len(), |i| start + i);
self.pos = end;
Some(&self.input[start..end])
}
}
fn main() {
let text = String::from("let x = 42");
let mut p = Parser::new(&text);
let first = p.next_word();
let second = p.next_word();
println!("{first:?} {second:?}");
let rest: Vec<&str> = std::iter::from_fn(|| p.next_word()).collect();
println!("{rest:?}");
}
Expected output:
Some("let") Some("x")
["=", "42"]
Had the signature been fn next_word(&mut self) -> Option<&str>, rule 3
would tie each word to the &mut self borrow, and holding first while
calling next_word again fails:
error[E0499]: cannot borrow `p` as mutable more than once at a time
'static and T: 'static
&'static str is a reference valid for the whole program — string literals
are the common case. The bound T: 'static means something different:
T contains no borrows shorter than 'static. Every owned type
(String, Vec<i32>, Arc<Mutex<_>>) satisfies it. It does not mean the
value lives forever.
std::thread::spawn requires 'static because the new thread could outlive
the caller's stack:
use std::fmt::Debug;
use std::thread;
fn log_in_background<T: Debug + Send + 'static>(value: T) -> thread::JoinHandle<()> {
thread::spawn(move || println!("{value:?}"))
}
fn main() {
log_in_background(vec![1, 2, 3]).join().unwrap(); // owned: fine
log_in_background("literal").join().unwrap(); // &'static str: fine
}
Expected output:
[1, 2, 3]
"literal"
Passing a borrow of a local String named local instead fails:
error[E0597]: `local` does not live long enough
The fix is to move an owned value in, or to use thread::scope.
Higher-ranked trait bounds (briefly)
Sometimes a bound must hold for every lifetime, not one chosen by the
caller. A function that calls a closure on its own local strings is the
example: no caller-supplied lifetime could name those locals. for<'a>
expresses this:
fn lengths<F>(f: F) -> (usize, usize)
where
F: for<'a> Fn(&'a str) -> &'a str,
{
let a = String::from("hello world");
let b = String::from("rust");
(f(&a).len(), f(&b).len())
}
fn first_word(s: &str) -> &str {
s.split(' ').next().unwrap_or("")
}
fn main() {
println!("{:?}", lengths(first_word));
}
This prints (5, 4). You seldom write for<'a> yourself: F: Fn(&str) -> &str is sugar for
exactly this bound. It appears explicitly in trait bounds such as
where for<'a> &'a T: IntoIterator.
Common mistakes
- Adding
'staticto make an error disappear.&'staticforces callers to pass leaked or literal data; usually you need an owned type instead. - Returning a reference to a local:
error[E0515]: cannot return reference to local variable. Return the owned value (String,Vec<T>). - Tying every reference to one
'awhen outputs borrow from just one input. - Storing references in long-lived structs (caches, app state). Owned data
or
Arcis usually simpler than threading a lifetime through everything.
Practice
Write struct Longest<'a> { best: &'a str } with offer(&mut self, s: &'a str)
that keeps the longer string, and get(&self) -> &'a str. Show that the
result of get can still be used after the Longest value is dropped.
Show solution
struct Longest<'a> {
best: &'a str,
}
impl<'a> Longest<'a> {
fn offer(&mut self, s: &'a str) {
if s.len() > self.best.len() {
self.best = s;
}
}
// Explicit 'a: the result borrows the strings, not the struct.
fn get(&self) -> &'a str {
self.best
}
}
fn main() {
let words = vec![String::from("ox"), String::from("zebra"), String::from("cat")];
let winner = {
let mut l = Longest { best: "" };
for w in &words {
l.offer(w);
}
l.get()
}; // `l` is dropped here; `winner` still borrows `words`
println!("{winner}");
}
This prints zebra.