Lesson 27 of 94
Lifetime Elision & 'static
You have written dozens of functions that take and return references without any 'a. That's because the compiler applies three simple elision rules to fill in lifetimes you leave out. Knowing the rules tells you exactly when an annotation is needed, and why an error mentions a lifetime you never wrote.
The second half of this lesson covers 'static, the longest lifetime, and the misconceptions around it.
The three elision rules
The compiler fills in missing lifetimes in function and method signatures using these rules, in order:
- Each reference parameter gets its own lifetime.
fn f(a: &str, b: &str)becomesfn f<'a, 'b>(a: &'a str, b: &'b str). - If there is exactly one input lifetime, every output reference gets it.
fn f(a: &str) -> &strbecomesfn f<'a>(a: &'a str) -> &'a str. - If one of the parameters is
&selfor&mut self, every output reference gets the lifetime ofself.
If output lifetimes are still unknown after applying the rules, the compiler reports E0106: missing lifetime specifier and asks you to write them.
1. Rule 1: every input gets its own lifetime
This function returns nothing borrowed, so rule 1 is all it needs.
fn same_length(a: &str, b: &str) -> bool {
a.len() == b.len()
}
// what the compiler sees:
fn same_length_explicit<'a, 'b>(a: &'a str, b: &'b str) -> bool {
a.len() == b.len()
}
fn main() {
println!("{} {}", same_length("rust", "java"), same_length_explicit("go", "java"));
}true false2. Rule 2: one input lifetime flows to the output
fn trim_prefix(s: &str) -> &str {
s.trim_start_matches("./")
}
// what the compiler sees:
fn trim_prefix_explicit<'a>(s: &'a str) -> &'a str {
s.trim_start_matches("./")
}
fn main() {
println!("{} {}", trim_prefix("./src/main.rs"), trim_prefix_explicit("././lib.rs"));
}src/main.rs lib.rs3. Rule 2 also covers references inside other types
The single input lifetime flows into every reference in the output: tuples, Option, Vec, and structs with lifetimes.
fn split_extension(file: &str) -> Option<(&str, &str)> {
file.rsplit_once('.')
}
fn words(text: &str) -> Vec<&str> {
text.split_whitespace().collect()
}
fn main() {
println!("{:?}", split_extension("archive.tar.gz"));
println!("{:?}", split_extension("Makefile"));
println!("{:?}", words("one two three"));
}Some(("archive.tar", "gz"))
None
["one", "two", "three"]4. Non-reference parameters don't count
Only references have lifetimes. A function with one reference and any number of owned or Copy parameters still has exactly one input lifetime, so rule 2 applies.
fn nth_word(text: &str, n: usize, uppercase_hint: bool) -> &str {
let _ = uppercase_hint;
text.split_whitespace().nth(n).unwrap_or("")
}
fn main() {
println!("{}", nth_word("zero one two three", 2, false));
}two5. Two input lifetimes: the rules give up
With two reference parameters, rule 2 does not apply and there is no self. The compiler has no way to know which input the output comes from.
fn pick(primary: &str, fallback: &str) -> &str {
if primary.is_empty() { fallback } else { primary }
}
fn main() {
println!("{}", pick("", "default"));
}error[E0106]: missing lifetime specifier
--> src/main.rs:1:43
|
1 | fn pick(primary: &str, fallback: &str) -> &str {
| ---- ---- ^ expected named lifetime parameter
|
= help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `primary` or `fallback`
help: consider introducing a named lifetime parameter
|
1 | fn pick<'a>(primary: &'a str, fallback: &'a str) -> &'a str {
| ++++ ++ ++ ++The fix is the annotation from Lifetimes in Functions: fn pick<'a>(primary: &'a str, fallback: &'a str) -> &'a str.
6. Rule 3: methods return references tied to self
In a method, rule 3 gives every output the lifetime of &self, even when there are other reference parameters. This is right for the most common case, a getter.
struct Catalog {
items: Vec<String>,
}
impl Catalog {
// elided: fn find<'s, 'q>(&'s self, query: &'q str) -> Option<&'s String>
fn find(&self, query: &str) -> Option<&String> {
self.items.iter().find(|item| item.contains(query))
}
}
fn main() {
let c = Catalog { items: vec![String::from("red chair"), String::from("blue table")] };
let found;
{
let q = String::from("table"); // shorter-lived query
found = c.find(&q);
}
println!("{:?}", found); // fine: the result is tied to `c`, not to `q`
}Some("blue table")7. When rule 3 guesses wrong
If a method returns a reference that comes from a parameter rather than from self, rule 3's guess is wrong, and the body fails to compile.
struct Formatter {
trim: bool,
}
impl Formatter {
fn clean(&self, input: &str) -> &str {
if self.trim { input.trim() } else { input }
}
}
fn main() {
let f = Formatter { trim: true };
println!("{}", f.clean(" hi "));
}error: lifetime may not live long enough
--> src/main.rs:7:46
|
6 | fn clean(&self, input: &str) -> &str {
| - - let's call the lifetime of this reference `'1`
| |
| let's call the lifetime of this reference `'2`
7 | if self.trim { input.trim() } else { input }
| ^^^^^ method was supposed to return data with lifetime `'2` but it is returning data with lifetime `'1`
|
help: consider introducing a named lifetime parameter and update trait if needed
|
6 | fn clean<'a>(&self, input: &'a str) -> &'a str {
| ++++ ++ ++8. Fixing it with an explicit lifetime
Write a lifetime that connects the output to the parameter it really comes from.
struct Formatter {
trim: bool,
}
impl Formatter {
fn clean<'a>(&self, input: &'a str) -> &'a str {
if self.trim { input.trim() } else { input }
}
}
fn main() {
let cleaned;
{
let f = Formatter { trim: true }; // the formatter can be short-lived
cleaned = f.clean(" hello ");
}
println!("[{cleaned}]");
}[hello]9. The anonymous lifetime '_
'_ means "a lifetime I don't want to name; use elision". It is used in two places:
- in types, to show that a struct borrows without naming the lifetime:
Parser<'_>, - in
implblocks that never refer to the lifetime by name:impl Parser<'_>.
struct Parser<'a> {
input: &'a str,
}
impl Parser<'_> {
fn remaining(&self) -> usize {
self.input.len()
}
}
fn make_parser(text: &str) -> Parser<'_> {
Parser { input: text.trim() }
}
fn describe(p: &Parser<'_>) -> String {
format!("{} bytes left", p.remaining())
}
fn main() {
let src = String::from(" let x = 1; ");
let p = make_parser(&src);
println!("{}", describe(&p));
}10 bytes left10. Elision in impl blocks with named lifetimes
When a method needs the struct's lifetime by name, keep impl<'a> Type<'a>. When it doesn't, impl Type<'_> is shorter. Both styles are common.
struct Tokens<'a> {
parts: Vec<&'a str>,
}
impl<'a> Tokens<'a> {
fn first(&self) -> Option<&'a str> {
self.parts.first().copied()
}
}
impl Tokens<'_> {
fn count(&self) -> usize {
self.parts.len()
}
}
fn main() {
let line = String::from("GET /health HTTP/1.1");
let t = Tokens { parts: line.split(' ').collect() };
println!("{:?} of {}", t.first(), t.count());
}Some("GET") of 3Part 2: 'static
11. String literals are &'static str
'static is the lifetime of the entire program. String literals are stored in the compiled binary, so a reference to one is valid forever.
fn main() {
let banner: &'static str = "Welcome to the app";
let copy_of_ref;
{
let inner: &'static str = banner;
copy_of_ref = inner;
}
println!("{copy_of_ref}");
}Welcome to the app12. Returning &'static str from a function
A function that returns one of several literals can promise 'static, so its result can be stored anywhere without borrowing anything.
fn reason_phrase(status: u16) -> &'static str {
match status {
200 => "OK",
201 => "Created",
204 => "No Content",
400 => "Bad Request",
404 => "Not Found",
500 => "Internal Server Error",
_ => "Unknown",
}
}
fn main() {
let mut log: Vec<&'static str> = Vec::new();
for code in [200, 404, 999] {
log.push(reason_phrase(code));
}
println!("{:?}", log);
}["OK", "Not Found", "Unknown"]13. static items give 'static references
A static variable lives for the whole program, so references to it are 'static.
static SUPPORTED_LOCALES: [&str; 4] = ["en-US", "en-GB", "de-DE", "ja-JP"];
fn default_locale() -> &'static str {
SUPPORTED_LOCALES[0]
}
fn all_locales() -> &'static [&'static str] {
&SUPPORTED_LOCALES
}
fn main() {
println!("default: {}", default_locale());
println!("all: {:?}", all_locales());
}default: en-US
all: ["en-US", "en-GB", "de-DE", "ja-JP"]14. Static lookup tables
Tables of constant data are a natural use of 'static: no allocation, no initialization at runtime, and shareable everywhere.
static COUNTRY_CODES: &[(&str, &str, u16)] = &[
("US", "United States", 1),
("GB", "United Kingdom", 44),
("DE", "Germany", 49),
("JP", "Japan", 81),
("BR", "Brazil", 55),
];
fn country(code: &str) -> Option<(&'static str, u16)> {
COUNTRY_CODES
.iter()
.find(|(c, _, _)| c.eq_ignore_ascii_case(code))
.map(|(_, name, dial)| (*name, *dial))
}
fn main() {
for code in ["de", "JP", "xx"] {
match country(code) {
Some((name, dial)) => println!("{code}: {name} (+{dial})"),
None => println!("{code}: unknown"),
}
}
}de: Germany (+49)
JP: Japan (+81)
xx: unknown15. Structs with 'static fields
A struct can hold &'static str fields without any lifetime parameter, because 'static is a specific lifetime, not a parameter. This is common for built-in error codes, command definitions, and metadata.
#[derive(Debug)]
struct ErrorCode {
code: &'static str,
message: &'static str,
http_status: u16,
}
const NOT_FOUND: ErrorCode = ErrorCode { code: "E404", message: "resource not found", http_status: 404 };
const RATE_LIMITED: ErrorCode = ErrorCode { code: "E429", message: "too many requests", http_status: 429 };
fn main() {
for e in [NOT_FOUND, RATE_LIMITED] {
println!("{} ({}): {}", e.code, e.http_status, e.message);
}
}E404 (404): resource not found
E429 (429): too many requests16. 'static does not fix borrow errors
When a borrow error appears, it is tempting to add 'static to the signature. That only makes the requirement stricter: now the argument must live for the whole program, and ordinary Strings don't.
fn remember(s: &'static str) -> &'static str {
s
}
fn main() {
let name = String::from("Ferris");
let kept = remember(&name);
println!("{kept}");
}error[E0597]: `name` does not live long enough
--> src/main.rs:7:25
|
6 | let name = String::from("Ferris");
| ---- binding `name` declared here
7 | let kept = remember(&name);
| ---------^^^^^-
| | |
| | borrowed value does not live long enough
| argument requires that `name` is borrowed for `'static`
8 | println!("{kept}");
9 | }
| - `name` dropped here while still borrowedThe compiler's help: consider using the 'static lifetime messages are occasionally right (for returning literals), but most of the time the real fix is ownership: return a String, or keep the data alive longer.
17. An owned String is not &'static str, but it can outlive everything
A common confusion: an owned String is not 'static data, but it does not borrow anything either, so it is free to live as long as its owner wants. For storing text long-term, String is almost always the answer, not &'static str.
fn build_banner(user: &str) -> String {
format!("*** Welcome, {user}! ***")
}
fn main() {
let banner: String;
{
let user = String::from("ferris");
banner = build_banner(&user);
}
println!("{banner}"); // owns its text: no lifetime involved
}*** Welcome, ferris! ***18. Leaking memory to get a &'static
Box::leak deliberately never frees a heap value, and in return gives a &'static reference to it. It is occasionally useful for data loaded once at startup and used for the rest of the program. Use it sparingly: leaked memory is never reclaimed.
fn load_config() -> &'static str {
let text = String::from("mode=production\nregion=eu-west-1"); // pretend this was read from a file
Box::leak(text.into_boxed_str())
}
fn main() {
let config: &'static str = load_config();
let mode = config.lines().next().unwrap();
println!("{mode}");
}mode=productioninto_boxed_str() converts the String to a Box<str>, and Box is covered in Box.
19. const vs static
Both give program-long values. A const is inlined at every use and has no fixed address; a static is a single location in memory. For &'static str values, either works; for large tables, prefer static so the data exists only once.
const GREETING: &str = "hello";
static FAREWELL: &str = "goodbye";
fn main() {
let a: &'static str = GREETING;
let b: &'static str = FAREWELL;
println!("{a} / {b}");
println!("same address every time: {}", std::ptr::eq(&FAREWELL, &FAREWELL));
}hello / goodbye
same address every time: true20. Reading lifetime error messages
Errors that mention lifetimes follow a few standard shapes. Recognizing them saves a lot of time:
| Message | What it means | Usual fix |
|---|---|---|
missing lifetime specifier (E0106) |
Elision couldn't decide what an output borrows from | Add <'a> and tie the output to the right input |
borrowed value does not live long enough (E0597) |
A reference is used after its value was dropped | Keep the value alive longer, or own the data |
cannot return reference to local variable (E0515) |
A function returns a reference to its own local | Return the owned value |
lifetime may not live long enough |
The body returns something the signature did not allow | Fix the signature, or return something else |
explicit lifetime required in the type of x (E0621) |
A parameter needs the output's lifetime | Add 'a to that parameter |
temporary value dropped while borrowed (E0716) |
A reference points into a temporary expression | Bind the temporary with let first |
The next example shows the last one, which surprises everyone once.
21. Temporaries are dropped at the end of the statement
A value created in the middle of an expression, such as String::from(...), is a temporary. It is dropped at the end of the statement unless it is bound to a variable.
fn main() {
let name: &str = String::from(" Ferris ").trim();
println!("{name}");
}error[E0716]: temporary value dropped while borrowed
--> src/main.rs:2:22
|
2 | let name: &str = String::from(" Ferris ").trim();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ - temporary value is freed at the end of this statement
| |
| creates a temporary value which is freed while still in use
3 | println!("{name}");
| ---- borrow later used here
|
help: consider using a `let` binding to create a longer lived value
|
2 ~ let binding = String::from(" Ferris ");
3 ~ let name: &str = binding.trim();
|fn main() {
let owned = String::from(" Ferris "); // bind the temporary...
let name: &str = owned.trim(); // ...then borrow from it
println!("{name}");
}Ferris22. Realistic example: a command table with 'static metadata
A command-line tool's commands are known at compile time. Storing their names and help texts as &'static str costs nothing at runtime.
struct Command {
name: &'static str,
aliases: &'static [&'static str],
help: &'static str,
}
static COMMANDS: &[Command] = &[
Command { name: "build", aliases: &["b"], help: "compile the project" },
Command { name: "test", aliases: &["t"], help: "run the tests" },
Command { name: "clean", aliases: &[], help: "remove build output" },
];
fn resolve(input: &str) -> Option<&'static Command> {
COMMANDS.iter().find(|c| c.name == input || c.aliases.contains(&input))
}
fn main() {
for input in ["b", "test", "deploy"] {
match resolve(input) {
Some(c) => println!("{input} -> {}: {}", c.name, c.help),
None => println!("{input} -> unknown command"),
}
}
}b -> build: compile the project
test -> test: run the tests
deploy -> unknown command23. Realistic example: MIME types with an elided lookup
The function takes a borrowed file name and returns a &'static str. The two lifetimes are unrelated: the result does not borrow the input at all, which is exactly what the signature says.
static MIME_TYPES: &[(&str, &str)] = &[
("html", "text/html"),
("css", "text/css"),
("js", "text/javascript"),
("json", "application/json"),
("png", "image/png"),
("svg", "image/svg+xml"),
];
fn mime_type(file_name: &str) -> &'static str {
let ext = file_name.rsplit_once('.').map(|(_, e)| e).unwrap_or("");
MIME_TYPES
.iter()
.find(|(e, _)| e.eq_ignore_ascii_case(ext))
.map(|(_, m)| *m)
.unwrap_or("application/octet-stream")
}
fn main() {
let mut served = Vec::new();
for name in ["index.HTML", "app.js", "logo.svg", "data.bin"] {
let owned_name = name.to_string();
served.push(mime_type(&owned_name)); // `owned_name` is dropped each iteration; the result isn't tied to it
}
println!("{:?}", served);
}["text/html", "text/javascript", "image/svg+xml", "application/octet-stream"]Summary
- Rule 1: each reference parameter gets its own lifetime.
- Rule 2: with exactly one input lifetime, outputs get it.
- Rule 3: in methods, outputs get the lifetime of
&self. - If outputs are still ambiguous, you write the lifetimes (
E0106). If rule 3 guesses wrong, name the lifetime of the parameter the result really comes from. '_means "elided here", in types (Parser<'_>) andimplblocks.'staticis the lifetime of the whole program: string literals,staticitems, and leaked data.- Adding
'staticto a signature makes it stricter, and almost never fixes a borrow error. Owned data is the usual fix.
Next: Fixing Borrow Checker Errors, a clinic of the errors you will meet most, with a fix for each.
