Step 3 of 7
The elision rules
You've written functions like fn first_word(s: &str) -> &str since the slices module, with no 'a anywhere. That works because of lifetime elision: a few rules the compiler applies to fill in lifetimes for the common cases. Only when the rules don't settle the question do you get E0106 and have to write them yourself.
Rule 1. Each reference parameter gets its own lifetime.
fn f(a: &str, b: &str) means fn f<'a, 'b>(a: &'a str, b: &'b str).
Rule 2. If there is exactly one input lifetime, every reference in the output gets it.
fn first_word(s: &str) -> &str means fn first_word<'a>(s: &'a str) -> &'a str.
Rule 3. If there's &self or &mut self, every reference in the output gets self's lifetime.
fn name(&self) -> &str means fn name<'a>(&'a self) -> &'a str.
| You write | After elision | |
|---|---|---|
fn len(s: &str) -> usize |
fn len<'a>(s: &'a str) -> usize |
no output reference: nothing to decide |
fn first(v: &[i32]) -> &i32 |
fn first<'a>(v: &'a [i32]) -> &'a i32 |
rule 2 |
fn get(&self, key: &str) -> &str |
output borrows from self |
rule 3 |
fn longest(a: &str, b: &str) -> &str |
two inputs, no self |
E0106: you decide |
fn make() -> &str |
no inputs at all | E0106: return owned data (or 'static, step 5) |
Rule 3 is what makes getters work:
struct Inventory {
items: Vec<String>,
}
impl Inventory {
fn find(&self, prefix: &str) -> Option<&str> {
for item in &self.items {
if item.starts_with(prefix) {
return Some(item);
}
}
None
}
}
fn main() {
let inv = Inventory { items: vec![String::from("sword"), String::from("shield")] };
println!("{:?}", inv.find("sh"));
println!("{:?}", inv.find("bow"));
}
Some("shield")
None
find has two references in (self and prefix), but rule 3 gives the result self's lifetime, which is exactly right: the item comes from the inventory, and prefix can be a short-lived string.
When rule 3 guesses wrong
Rule 3 assumes a method's result borrows from self. If it can come from another argument, the guess is wrong and the compiler tells you:
struct Config {
default_label: String,
}
impl Config {
fn label_for(&self, custom: &str) -> &str {
if custom.is_empty() { &self.default_label } else { custom }
}
}
fn main() {
let c = Config { default_label: String::from("untitled") };
println!("{}", c.label_for(""));
}
error: lifetime may not live long enough
--> src/main.rs:7:61
|
6 | fn label_for(&self, custom: &str) -> &str {
| - - let's call the lifetime of this reference `'1`
| |
| let's call the lifetime of this reference `'2`
7 | if custom.is_empty() { &self.default_label } else { custom }
| ^^^^^^ method was supposed to return data with lifetime `'2` but it is returning data with lifetime `'1`
The compiler names the two elided lifetimes '1 and '2 to explain: the signature (via rule 3) promised data living as long as self ('2), but custom lives for '1. Since this result can come from either, both need the same name: fn label_for<'a>(&'a self, custom: &'a str) -> &'a str.
The anonymous lifetime '_
Sometimes a type has a lifetime parameter but you don't need to name it. Write '_, "some lifetime, figured out by elision". You've seen it in fmt::Formatter<'_>, and you'll use it for structs with references in the next step.
Common mistakes
- Expecting the compiler to look at the body.
fn pick(a: &str, b: &str) -> &strneeds annotations even if the body only ever returnsa: elision works from the signature alone. Writefn pick<'a>(a: &'a str, b: &str) -> &'a strto say so. - Trusting rule 3 for every method. It's right for getters, wrong when the result can come from another argument (the
label_forerror above). - Annotating everything. See the note below.
Workplace note
Don't write lifetimes the rules already cover. Clippy's needless_lifetimes lint flags fn first<'a>(v: &'a [i32]) -> &'a i32 and suggests the shorter form. Real Rust code has far fewer 'as than beginners expect.
Your task
This file has four functions and only some of them need lifetime annotations. Add annotations only where elision can't decide (the compiler's E0106 errors tell you which), and leave the others unannotated.
Challenge 2
Fill in the blanks to write out, by hand, the lifetimes that elision fills in for you (use 'a). The program prints Ferris. In real code you'd leave these out: clippy would call them needless.
Challenge 3
Write the method label_for on Config: it returns custom unless it's empty, in which case it returns the config's default_label. Rule 3 alone gets this wrong (it assumes the result always comes from self), so annotate it yourself.
Previous: Lifetime annotations on functions Next: Structs that hold references