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:
- Every reference parameter gets its own lifetime.
- If there is exactly one input lifetime, the result gets it.
- In a method with
&selfor&mut self, the result gets the lifetime ofself.
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
'aon every reference parameter. The caller then fails with E0597 for whichever argument dies first.- Returning a reference to a local, or storing
Stringto dodge the lifetime. The first iserror[E0515]: cannot return reference to local variable; the second copies every line and cannot give back anOption<&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.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…