Rust Struct Lifetime Patterns
A struct lifetime pattern is the set of lifetime letters on a struct. Each letter names how long one borrowed field must stay valid.
Two references, two letters
first is text written in code, good for the whole program. second borrows a local String that dies with main. Neither promise has anything to do with the other, so each gets its own letter:
struct Pair<'a, 'b> {
first: &'a str,
second: &'b str,
}
let local = String::from("short note");
let p = Pair { first: "written in code", second: &local };
println!("{} / {}", p.first, p.second);A struct cannot smuggle a local out
make_pair builds a Pair from a String made inside itself, while the return type claims both fields are 'static. The local dies at the closing brace, so the claim is refused:
struct Pair<'a, 'b> { first: &'a str, second: &'b str }
fn make_pair() -> Pair<'static, 'static> {
let local = String::from("made inside");
Pair { first: "written in code", second: &local } // error E0515: cannot return value referencing local variable `local`
}Owned and borrowed side by side
title is owned text that lives and dies with the struct. body is borrowed from a String owned elsewhere. The struct needs a letter only for the borrowed field:
struct Note<'a> {
title: String,
body: &'a str,
}
let page = String::from("dear diary, today we borrowed");
let note = Note { title: String::from("Day 1"), body: &page };
println!("{}: {}", note.title, note.body);Methods hand pieces back
first() can promise its field's own letter 'a, because it always returns that field. longest() may return either field, so its output takes the lifetime of &self instead — elision Rule 3 at work:
impl<'a, 'b> Pair<'a, 'b> {
fn first(&self) -> &'a str {
self.first
}
fn longest(&self) -> &str {
if self.first.len() >= self.second.len() { self.first } else { self.second }
}
}
let local = String::from("tiny");
let p = Pair { first: "a much longer headline", second: &local };
println!("first = {}", p.first());
println!("longest = {}", p.longest());Example
struct Article<'a, 'b> {
headline: &'a str,
source: &'b str,
tags: Vec<String>,
}
impl<'a, 'b> Article<'a, 'b> {
fn summary(&self) -> String {
format!("{} (via {}, {} tags)", self.headline, self.source, self.tags.len())
}
}
fn main() {
let wire = String::from("the evening wire");
let article = Article {
headline: "Borrow checker praised by local programmers",
source: &wire,
tags: vec![String::from("rust"), String::from("safety")],
};
println!("{}", article.summary());
println!("headline = {}", article.headline);
}Key points
- Give each reference its own letter.
- Do it when the two may die at different times.
- struct Pair<'a, 'b> keeps the two promises separate.
- Sharing one letter is also a promise.
- Both references must live at least as long as the shorter of the two.
- Write two letters when you cannot promise that.
- Owned and borrowed fields mix freely in one struct.
- The owned fields die with the struct.
- Only the borrowed fields need letters.
- An impl block repeats the struct's letters.
- It writes impl<'a, 'b> Pair<'a, 'b>.
- Each method may add letters of its own.
- Those letters are for its own inputs.
- A method that may return either of two fields writes no letter on the output.
- Elision Rule 3 ties the output to &self.
- A generic field adds a bound instead of another letter.
- The form is struct Holder<'a, T: 'a>.
- In short, the letters on a struct name how long each borrowed field must stay valid.
In this example, Article mixes all three field kinds. headline borrows text written in code, so it is good for the whole program. source borrows the local String wire. That String lives only until the end of main. It is a different promise. So source gets the second letter, 'b. tags is owned outright and needs no letter at all. summary builds a fresh String from all three fields. The two println! lines show the mix working. The summary line uses the borrowed headline. It uses the borrowed source. And it uses the owned tag count. The Lifetime Elision lesson shows the rule that ties a method's output to &self. The Lifetime Bounds lesson covers the bound form, struct Holder<'a, T: 'a>. The Subtyping lesson in Rust Advanced explains why the longer-lived type may stand in for the shorter.
Note
The letters on a struct are part of its type.
Pair<'static, 'a> and Pair<'a, 'a> are different types.
The longer-lived one may stand in for the shorter.
Try it Yourself
Edit the code, then press Run.
Running is simulated · Rust Playground
Exercise
A struct holds two references whose owners may drop them at completely different times. How should the struct be declared?