How it works
The explanation, with examples you can run.
When you have to write them
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() >= y.len() { x } else { y }} struct Parser<'src> { input: &'src str, // a Parser cannot outlive the text it points into} impl<'src> Parser<'src> { fn first_word(&self) -> &'src str { self.input.split_whitespace().next().unwrap_or("") }} fn first_char(s: &str) -> &str { // elided: same as fn first_char<'a>(s: &'a str) -> &'a str &s[..1]} fn main() { let a = String::from("ownership"); let w; { let b = String::from("borrow"); println!("{}", longest(&a, &b)); // fine: used while both are alive let p = Parser { input: &a }; w = p.first_word(); // tied to a, not to p } println!("{w} {}", first_char("rust"));}It prints ownership, then ownership r. Without the annotations, longest fails with:
error[E0106]: missing lifetime specifier |1 | fn longest(x: &str, y: &str) -> &str { | ---- ---- ^ expected named lifetime parameter | = help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `x` or `y`With 'a on both inputs, the result is valid for the region where both are valid. The caller can't keep the result after either string is dropped.
first_word returns &'src str, not a borrow of self. That is why w stays valid after p is gone: it points into a, which is still alive. Choosing which lifetime an output carries is real API design.
Elision rules
Most signatures need no annotations because of three elision rules:
- Each elided lifetime in the inputs gets its own parameter.
- If there is exactly one input lifetime, it is used for every elided output lifetime.
- If one input is
&selfor&mut self, its lifetime is used for elided outputs.
If the rules don't settle an output's lifetime, you must write it. Since Rust 1.89 a lint, mismatched_lifetime_syntaxes, warns when a signature hides a lifetime in one place but shows it in another, such as fn make(s: &str) -> Thing where Thing secretly borrows from s; writing -> Thing<'_> makes the borrow visible.
'static
'static means the data lives until the program ends. String literals are &'static str. As a bound, T: 'static means the type holds no borrowed data that could expire. Owned types like String satisfy it. That is why std::thread::spawn and tokio::spawn require it: the new thread or task may outlive the caller's stack.
When lifetimes fight you
If a struct needs several lifetime parameters, or you keep reaching for 'static to make errors go away, the data probably wants to be owned: store a String, or share it with Rc or Arc. Borrowing structs work best for short-lived views such as parsers, iterators and guards.
Check yourself
3 questions. Get them all right to finish this topic.
0 of 3 right so far. Get all three to finish this topic.