Lifetimes appear in signatures as soon as a function returns a reference or a struct holds one.
Every reference has a lifetime: the region of code during which it is valid. Usually the compiler infers them. Annotations are needed when a function's signature alone does not say which input a returned reference comes from.
fn longer<'a>(x: &'a str, y: &'a str) -> &'a strreads as: "for some lifetime 'a, both inputs live at least as long as 'a, and the result is valid for 'a". In practice the result is valid only as long as the shorter of the two inputs. Annotations never change how long anything lives; they describe relationships so the borrow checker can verify callers.
You rarely write them, thanks to the elision rules:
- Each reference parameter gets its own lifetime.
- If there is exactly one input lifetime, it is assigned to all output references.
- In methods with
&selfor&mut self, outputs get the lifetime ofself.
So fn first_word(s: &str) -> &str needs nothing, but longer above does, because there are two inputs and no self.
A struct that holds a reference must declare a lifetime parameter: struct Parser<'a> { input: &'a str }. A Parser then cannot outlive the string it reads. In impl<'a> Parser<'a> you can return &'a str slices that stay valid even after the parser itself is dropped.
'static means "valid for the whole program": string literals are &'static str. Seeing a compiler suggestion to add 'static is usually a hint that you should return an owned value instead.
When fighting lifetimes in your own structs, ask whether the struct should simply own its data (String instead of &str). Borrowing structs are great for short-lived views and parsers, not for long-lived state.
Example
fn longer<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() >= y.len() { x } else { y }
}
struct Parser<'a> { input: &'a str, pos: usize }
impl<'a> Parser<'a> {
// returns a slice of the input, valid for 'a, not tied to the &mut self borrow
fn next_word(&mut self) -> Option<&'a str> {
let rest = self.input[self.pos..].trim_start();
if rest.is_empty() { return None; }
let start = self.input.len() - rest.len();
let end = rest.find(' ').map_or(self.input.len(), |i| start + i);
self.pos = end;
Some(&self.input[start..end])
}
}
fn main() {
let text = String::from("borrow checker rules");
let mut p = Parser { input: &text, pos: 0 };
let a = p.next_word().unwrap();
let b = p.next_word().unwrap(); // `a` stays valid: it borrows text, not p
println!("{}", longer(a, b)); // checker
}Remember
- Lifetime annotations describe relationships between references; they never extend a value's life
- Elision covers single-input and
&selfmethods; otherwise annotate - Structs holding references need
<'a>and cannot outlive the borrowed data
Watch out for
- Annotating
-> &'static strto silence an error, when returning an ownedStringis the real fix.