Lifetimes in Structs
A lifetime parameter on a struct declares that an instance of the struct cannot outlive the references stored in its fields. Whenever a struct holds a non-'static reference (&'a T or &'a mut T), the struct definition must declare a lifetime parameter in angle brackets (struct Name<'a>).
Syntax and Memory Layout
When a struct holds a reference instead of an owned value, it avoids copying or heap-allocating the underlying data, but its validity becomes tied to the borrowed owner:
Stack (Owner `text: String`) Heap Buffer
+--------------------------+ +---------------------+
| ptr --------------------+-----> | Call me Ishmael. ...|
| len = 41, cap = 41 | +---------------------+
+--------------------------+ ^
|
Stack (`Excerpt<'a>`) |
+--------------------------+ |
| part: &'a str | |
| ptr -------------------+-------------+
| len = 16 |
+--------------------------+
Constraint: `Excerpt<'a>` cannot outlive `text`.
#[derive(Debug)]
struct Excerpt<'a> {
part: &'a str,
}
fn main() {
let novel = String::from("Call me Ishmael. Some years ago...");
let first_sentence = novel.split('.').next().unwrap();
let excerpt = Excerpt {
part: first_sentence,
};
println!("Excerpt: {:?}", excerpt.part);
}
Excerpt: "Call me Ishmael"
Why Struct Lifetime Annotations Are Required
If a struct could store a reference without a lifetime parameter, the compiler could not verify at function boundaries whether the referenced data is still alive when the struct is used. Omitting the lifetime parameter produces E0106:
// ❌ does not compile
struct BadExcerpt {
part: &str,
}
fn main() {}
error[E0106]: missing lifetime specifier
--> src/main.rs:3:11
|
3 | part: &str,
| ^ expected named lifetime parameter
|
help: consider introducing a named lifetime parameter
|
2 ~ struct BadExcerpt<'a> {
3 ~ part: &'a str,
|
Preventing Dangling Struct Instances
The borrow checker ensures that a struct instance Excerpt<'a> is never used after the data borrowed by 'a is dropped:
// ❌ does not compile
struct Excerpt<'a> {
part: &'a str,
}
fn main() {
let excerpt: Excerpt;
{
let short_lived = String::from("Temporary string");
excerpt = Excerpt {
part: &short_lived,
};
} // `short_lived` is dropped here
println!("{}", excerpt.part);
}
error[E0597]: `short_lived` does not live long enough
--> src/main.rs:11:19
|
9 | let short_lived = String::from("Temporary string");
| ----------- binding `short_lived` declared here
10 | excerpt = Excerpt {
11 | part: &short_lived,
| ^^^^^^^^^^^^ borrowed value does not live long enough
12 | };
13 | } // `short_lived` is dropped here
| - `short_lived` dropped here while still borrowed
14 | println!("{}", excerpt.part);
| ------------ borrow later used here
Structs with Multiple Borrowed Fields
A struct with multiple reference fields can either share one lifetime parameter 'a (meaning the struct's lifetime is bounded by the intersection—the shorter—of all borrowed inputs) or use distinct lifetime parameters ('a, 'b) when the fields have independent lifetimes:
#[derive(Debug)]
struct KeyValue<'a> {
key: &'a str,
value: &'a str,
}
fn main() {
let raw_line = String::from("host=localhost");
let (k, v) = raw_line.split_once('=').unwrap();
let kv = KeyValue { key: k, value: v };
println!("{} -> {}", kv.key, kv.value);
}
host -> localhost
Owned Fields vs. Borrowed Fields in Structs
| Design | Field Type | Trade-off |
|---|---|---|
| Owned struct | String, Vec<T> |
Easier to store, return, and move across threads; may require heap allocation or cloning |
| Borrowed view struct | &'a str, &'a [T] |
Zero-copy parsing and slicing; cannot outlive the backing buffer |