Skip to content
Lesson 2 of 9

Step 1 of 7 · Reading · ~4 min

Learn

Ownership Patterns

Lifetimes: Signatures and Structs

By the end you can read and write the 'a in a function signature or a struct, and say which argument a returned reference may outlive. You already hand out slices and &str borrows and know a borrow ends at its last use; this lesson covers how the compiler tracks a loan across a function boundary or inside a struct.

The line the checker draws

A lifetime is the stretch of code during which a reference must stay valid. The compiler infers almost all of them and objects when a reference outlives its value:

let r;
{
    let x = 5;
    r = &x;
}
println!("{}", r);
error[E0597]: `x` does not live long enough
  |     - `x` dropped here while still borrowed
  |                    - borrow later used here

Saying where a result comes from

A caller is checked against the callee's signature, never its body, so a function that takes several references and returns one must say which input the result is cut from:

fn first_of(a: &str, b: &str) -> &str { /* ... */ }
error[E0106]: missing lifetime specifier
  = help: this function's return type contains a borrowed value, but the
          signature does not say whether it is borrowed from `a` or `b`

<'a> declares a lifetime name and &'a str reads "a reference valid for 'a". Put the name on the input the result comes from and leave the others bare:

fn tag_of<'a>(entry: &'a str, sep: &str) -> &'a str {
    entry.split(sep).next().unwrap_or(entry)
}

let entry = String::from("warn:disk almost full");
let tag;
{
    let sep = String::from(":");
    tag = tag_of(&entry, &sep);
}
println!("{}", tag);
warn

sep is dropped at the closing brace and tag is still fine, because the signature says the result borrows from entry only. Writing sep: &'a str as well ties the result to sep too, and the same caller fails with E0597 on sep. The body is checked too: returning sep from tag_of gives E0621: explicit lifetime required in the type of sep``. An annotation never keeps anything alive longer; it only states a relationship the compiler can check.

When you can leave the names out

The compiler first applies three elision rules:

  1. Every reference parameter gets its own lifetime.
  2. If there is exactly one input lifetime, the result gets it.
  3. In a method with &self or &mut self, the result gets the lifetime of self.

fn head(s: &str) -> &str needs nothing; first_of has two inputs and no self, so it fails. The catch with rule 3: in fn pick(&self, other: &str) -> &str the result is assumed to borrow from self, so a body returning other fails with E0623: lifetime mismatch.

Structs that hold references

A struct that stores a reference carries the lifetime in its name, and its impl declares it once:

struct Entry<'a> {
    tag: &'a str,
    body: &'a str,
}

impl<'a> Entry<'a> {
    fn parse(line: &'a str) -> Option<Entry<'a>> {
        let i = line.find(':')?;
        Some(Entry { tag: &line[..i], body: &line[i + 1..] })
    }

    fn is_warning(&self) -> bool {
        self.tag == "warn"
    }
}

let line = String::from("warn:disk almost full");
let e = Entry::parse(&line).unwrap();
println!("{} / {} / {}", e.tag, e.body, e.is_warning());
warn / disk almost full / true

Nothing is copied: e is two pointers into line, so dropping line while e is still used is an error. String literals are the opposite case. They live inside the compiled program, so their type is &'static str, valid for the whole run.

Common mistakes

  • 'a on every reference parameter. The caller then fails with E0597 for whichever argument dies first.
  • Returning a reference to a local, or storing String to dodge the lifetime. The first is error[E0515]: cannot return reference to local variable; the second copies every line and cannot give back an Option<&str> once the container is gone.
  • Comparing len() when you mean characters. "añ".len() is 3 and "añ".chars().count() is 2, so a line of 6 characters can look longer than one of 8.
  • Replacing on a tie. >= picks the later of two equally long lines; the rule says the first.

Your exercise

Write a tiny grep whose hits borrow from the text and not from the needle. The starter reads the input and drops the needle inside a block before the totals are printed, so only a signature tied to the text alone compiles.

Up nextTraits and GenericsTraits and Generics

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…