A struct field cannot be a plain &str:
struct Excerpt {
part: &str, // error[E0106]: missing lifetime specifier
}
Every reference has a lifetime: the region of the program during which the
data it points at must stay valid. In a function signature the compiler can
often fill in a missing lifetime from the parameters (the elision rules). A
struct definition has no parameters to take one from, so the compiler asks you
to name it, and its suggested fix is a lifetime parameter, struct Excerpt<'a>.
Once you add that parameter, it tends to appear elsewhere too: on every struct that stores the first one, and on the functions that return it. This article explains what the parameter promises, why it spreads, and how to decide when a struct should own its data instead.
The declaration
struct ImportantExcerpt<'a> {
part: &'a str,
}
'a is a lifetime parameter, a name for some region of the program. The
field type &'a str says the borrowed string must stay valid for all of that
region. Each time you create or use an ImportantExcerpt, the compiler picks a
concrete region for 'a, usually by inference, so you rarely write one out.
The practical guarantee:
A value of type
ImportantExcerpt<'a>cannot be used after the data itspartfield borrows has been dropped.
The compiler enforces this without any further annotations:
fn main() {
let excerpt;
{
let novel = String::from("Call me Ishmael. Some years ago...");
excerpt = ImportantExcerpt { part: &novel[..15] };
}
println!("{}", excerpt.part);
// error[E0597]: `novel` does not live long enough
}
&novel[..15] borrows the first 15 bytes of novel, the text
Call me Ishmael. (A string slice range counts bytes, not characters; it works
here because the text is ASCII.) novel is dropped at the end of the inner
block, but excerpt is used after that, so the borrow would outlive its data.
Moving the println! inside the block, or declaring novel in the outer
scope, makes the program compile.
The lifetime parameter is part of the type
ImportantExcerpt on its own is a generic type, like Vec without its
element type. The complete type is ImportantExcerpt<'a> for some lifetime
'a. An impl block must therefore declare the parameter before using it:
impl<'a> ImportantExcerpt<'a> {
// Named: the result borrows the original text, not `self`,
// so it can still be used after this excerpt is dropped.
fn text(&self) -> &'a str {
self.part
}
}
impl ImportantExcerpt<'_> {
// `'_` says "there is a lifetime here; this block does not name it".
fn word_count(&self) -> usize {
self.part.split_whitespace().count()
}
}
Leaving the parameter off an impl header is an error:
impl ImportantExcerpt {
// ^^^^^^^^^^^^^^^^ error[E0726]: implicit elided lifetime not allowed here
// expected lifetime parameter
}
The compiler's help message suggests impl ImportantExcerpt<'_>.
Function signatures are more lenient. fn first(text: &str) -> ImportantExcerpt
compiles, because elision supplies the hidden lifetime from text. Since Rust
1.89, rustc warns about this spelling by default (the
mismatched_lifetime_syntaxes lint: "hiding a lifetime that's elided elsewhere
is confusing"). Writing ImportantExcerpt<'_> keeps the borrow visible to
readers.
When an impl names a lifetime it never needs, Clippy's
elidable_lifetime_names lint suggests the '_ form. That lint is in the
pedantic group, so it reports nothing unless you enable it.
Check your understanding
Does struct Foo<'a> { .. } mean "a Foo lives for 'a"?
Show answer
Hide answer
No. It means every reference stored in a Foo<'a> is valid for at least the
region 'a. A consequence is that a Foo<'a> value cannot be used after 'a
ends. It can be dropped much earlier, like any other value.
The two readings make different predictions:
- If
Foo<'a>meant "lives for'a", a value of typeFoo<'static>would have to exist until the program ends. It does not:let e = ImportantExcerpt { part: "literal" };can have typeImportantExcerpt<'static>, andeis dropped at the end of its scope like any local variable. - If it means "holds references valid for at least
'a", aFoo<'long>can be used where aFoo<'short>is expected, because a reference that is valid for a long region is also valid for any shorter region inside it. This is what happens forImportantExcerpt.
fn longer<'a>(x: ImportantExcerpt<'a>, y: ImportantExcerpt<'a>) -> ImportantExcerpt<'a> {
if x.part.len() >= y.part.len() { x } else { y }
}
fn main() {
let fixed: ImportantExcerpt<'static> = ImportantExcerpt { part: "static text" };
let owned = String::from("local");
let local = ImportantExcerpt { part: &owned };
let chosen = longer(fixed, local); // `fixed` is used at the shorter lifetime
println!("{}", chosen.part);
}
The technical term is variance. A struct whose only use of 'a is a shared
reference field such as &'a str is covariant in 'a: Foo<'long> is a
subtype of Foo<'short>, so the compiler shortens the lifetime where needed.
Not every struct behaves this way. A field such as &'a mut &'a str or
Cell<&'a str> makes the struct invariant in 'a, and then the lifetime
cannot be shortened.
Why the parameter spreads
A struct that stores an ImportantExcerpt<'a> stores the same borrow, so it
is subject to the same limit and needs the same parameter:
struct Chapter<'a> {
title: String,
opening: ImportantExcerpt<'a>,
}
Writing opening: ImportantExcerpt without <'a> on Chapter is E0106 again.
The only way to store an excerpt without a parameter is to fix the lifetime,
for example ImportantExcerpt<'static>. That compiles, but the field can then
hold only excerpts of data that lives for the whole program, such as string
literals.
A helpful way to think about this: a struct with a lifetime parameter is a
view onto data that someone else owns. A Vec<String> owns its contents, so
it can be stored or returned anywhere its owner chooses. An
ImportantExcerpt<'a>, or a parser or tokenizer holding a &'a str, can only
look at text that is still alive. A type that stores a view (other than a
'static one) is itself a view of the same data. The parameter spreads because
that limit spreads; the compiler is reporting a real constraint.
Deciding whether to borrow or own
Borrowing fits when the struct is used only while the source data is alive and in scope, for example:
- during a single parse, scan or traversal;
- created, used and dropped inside one function, or passed down a call chain whose caller owns the data;
- when avoiding a copy matters for your program (measure it if performance is the reason).
Owning is needed when the struct must:
- outlive the source data, for example by being stored in a collection, cache or configuration object that lives longer than the source;
- be returned from the function that owns the source data;
- move into a context that requires
'static, such as a closure passed tostd::thread::spawn, aBox<dyn Trait>in a position where the default object bound is'static(such as a return type or a struct field), or a global registry.
Some of these have borrowing alternatives: std::thread::scope lets threads
borrow data from the enclosing function, and Box<dyn Trait + 'a> allows a
trait object that borrows. They work only while the borrowed data is still
alive.
If a type with a lifetime parameter needs to be stored somewhere that outlives its source, that is the signal to own the data (or use one of the alternatives below). The question to answer is "what has to outlive what", not "which is faster".
Check your understanding
You are writing a log analyzer. It parses each line LEVEL message into a record, and the caller wants all the records in a Vec to sort and query afterwards. Should Record borrow from the input or own its strings?
Show answer
Hide answer
struct Record<'a> { level: &'a str, message: &'a str }
// ...or...
struct Record { level: String, message: String }
It depends on one question: does the input text stay alive at least as long as the Vec<Record>?
If the caller reads the whole file into one String, keeps it alive, and then parses it, Record<'a> works. Each record holds two slices into that buffer, so parsing allocates no separate String per field (the Vec still allocates its own storage).
fn parse(text: &str) -> Vec<Record<'_>> {
text.lines()
.filter_map(|line| line.split_once(' '))
.map(|(level, message)| Record { level, message })
.collect()
}
If instead a function reads the file into a local String and returns the records, the buffer is dropped when the function returns, and the borrowing version cannot compile:
fn load(path: &str) -> std::io::Result<Vec<Record<'static>>> {
let text = std::fs::read_to_string(path)?;
Ok(parse(&text)) // error[E0515]: cannot return value referencing local variable `text`
}
The same applies when lines are read one at a time into a reused buffer, or when records must move into a std::thread::spawn thread. No lifetime annotation fixes these cases, because the data really is gone or may be. Own the strings.
Cow<'a, str> (from std::borrow) helps in a different situation: when most fields can be borrowed unchanged but some must be modified, for example to replace escape sequences. A Cow is either Borrowed(&'a str) or Owned(String), so only the changed fields allocate. It still carries 'a, so it does not remove the need for the input to outlive the records; Cow<'static, str> or String does.
Alternatives when a view will not do
1. Own the data. Use String instead of &str, Vec<T> instead of
&[T]. Creating one usually costs an allocation and a copy of the contents.
In return the type has no lifetime parameter, so it can be stored, returned and,
if its contents are Send, moved to another thread.
2. Shared ownership with Rc<T> or Arc<T>. Several owners hold pointers
to one heap value. Cloning an Rc or Arc increments a reference count instead
of copying the data, and the value is dropped when the last owner is dropped.
Rc is for a single thread (it is not Send); Arc uses atomic counts so it
can be shared across threads when T is Send and Sync. Both give shared
access through &T, so mutating a value that has several owners needs Cell,
RefCell, Mutex or similar inside (Rc::get_mut and Arc::get_mut work
only while there is a single owner). A cycle of Rc or Arc pointers is not
freed automatically: make one link a Weak pointer, or break the cycle by
hand before the last outside owner is dropped.
3. Indices into an arena. Keep all values in one collection that a single struct owns, and have them refer to each other by index instead of by reference:
struct Node {
name: String,
parent: Option<usize>,
children: Vec<usize>,
}
struct Tree {
nodes: Vec<Node>,
}
impl Tree {
fn add(&mut self, name: &str, parent: Option<usize>) -> usize {
let id = self.nodes.len();
self.nodes.push(Node { name: name.to_string(), parent, children: Vec::new() });
if let Some(p) = parent {
self.nodes[p].children.push(id);
}
id
}
}
Tree has no lifetime parameter, and parent links (which would be awkward
with references) are just numbers. The costs: every access needs the arena at
hand, indexing is bounds-checked and panics on an out-of-range index, and the
borrow checker no longer verifies that an index refers to the element you
meant. If elements are removed or slots reused, an old index can silently
refer to a different element; generational indices are a common fix.
One lint to know about
When a struct has both a lifetime and a type parameter, you might spell out their relationship:
pub struct S<'a, T: 'a> { d: &'a T }
T: 'a means "any references inside T are valid for at least 'a". Since
Rust 1.31, the compiler infers this bound from the field type: a
&'a T field already requires T: 'a, so writing it adds nothing. rustc's
explicit_outlives_requirements lint points this out. It is allow-by-default,
so you see it only after enabling it, for example with
#![warn(explicit_outlives_requirements)]:
warning: outlives requirements can be inferred
|
2 | pub struct S<'a, T: 'a> { d: &'a T }
| ^^^^ help: remove this bound
Summary
- A struct that stores a reference needs a lifetime parameter, because a struct definition has no inputs for elision to borrow a lifetime from.
struct Foo<'a>means "holds references valid for at least'a", not "lives for'a". AFoo<'a>cannot be used after'aends, but can be dropped earlier.Foo<'a>is the complete type, so the parameter goes on theimplheader too (impl<'a> Foo<'a>orimpl Foo<'_>).- A struct with a lifetime parameter is a view. Any type that stores a
non-
'staticview needs the parameter too, and none of them can be used after the viewed data is gone. - Borrow for work done while the source is alive; own for storage that
outlives the source, for returning past the owner, and for
'staticcontexts such asstd::thread::spawn. - When you must own:
StringorVec,RcorArc, or indices into an arena.