RustArena

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

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