Rust lifetimes
last modified October 25, 2026
In this article we explain lifetimes in Rust.
A lifetime is the region of code in which a reference is valid. Every reference in Rust has a lifetime. The compiler's borrow checker compares lifetimes to make sure that no reference outlives the data it points to. Such a reference would be a dangling reference, a classic source of crashes and security bugs in C and C++.
Most of the time lifetimes are inferred and we never write them. We have to
annotate them when the compiler cannot figure out how the lifetimes of
several references relate to each other. Lifetime annotations start with an
apostrophe, such as 'a. They do not change how long any value
lives; they describe relationships that the compiler then checks. Ownership
and borrowing, on which lifetimes build, are covered in
Rust ownership.
The examples were tested with Rust 1.97 and the 2024 edition. They are run as
Cargo projects with cargo run -q. Several examples do not
compile on purpose; for them we show the compiler error.
Dangling references
The main purpose of lifetimes is to prevent dangling references.
fn main() {
let r;
{
let x = 5;
r = &x;
}
println!("r: {r}");
}
The variable r is declared in the outer scope and assigned a
reference to x in the inner scope.
{
let x = 5;
r = &x;
}
The variable x is dropped at the end of the inner block, but
r is used after it. The reference would point to freed memory.
The borrow checker sees that the lifetime of r is longer than
that of x and rejects the program.
$ cargo run -q
error[E0597]: `x` does not live long enough
--> src/main.rs:6:13
|
5 | let x = 5;
| - binding `x` declared here
6 | r = &x;
| ^^ borrowed value does not live long enough
7 | }
| - `x` dropped here while still borrowed
8 |
9 | println!("r: {r}");
| - borrow later used here
For more information about this error, try `rustc --explain E0597`.
Lifetimes in function signatures
A function that returns a reference must tell the compiler which input the reference comes from.
fn longest(x: &str, y: &str) -> &str {
if x.len() > y.len() { x } else { y }
}
fn main() {
let a = String::from("falcon");
let b = "owl";
println!("{}", longest(&a, b));
}
The longest function returns the longer of two string slices.
fn longest(x: &str, y: &str) -> &str {
The returned reference is borrowed either from x or from
y, and which one is decided at run time. The compiler checks
each function on its own, using only the signature, so it cannot tell how
long the result stays valid.
$ cargo run -q
error[E0106]: missing lifetime specifier
--> src/main.rs:1:33
|
1 | fn longest(x: &str, y: &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 `x` or `y`
help: consider introducing a named lifetime parameter
|
1 | fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
| ++++ ++ ++ ++
For more information about this error, try `rustc --explain E0106`.
Lifetime annotations
We add a lifetime parameter, as the compiler suggests.
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
fn main() {
let a = String::from("falcon");
let b = "owl";
println!("{}", longest(&a, b));
let outer = String::from("a long string");
{
let inner = String::from("short");
let res = longest(&outer, &inner);
println!("{res}");
}
}
Lifetime parameters are declared in angle brackets, like generic type parameters.
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
The signature says: for some lifetime 'a, both parameters live
at least as long as 'a, and the returned reference is valid for
'a as well. In practice, 'a becomes the shorter of
the two input lifetimes.
let outer = String::from("a long string");
{
let inner = String::from("short");
let res = longest(&outer, &inner);
println!("{res}");
}
Here 'a is the lifetime of inner. The result is
used inside the inner block, where both strings are alive, so the program
compiles.
$ cargo run -q falcon a long string
The shorter lifetime wins
The annotation lets the compiler check every call of the function.
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
fn main() {
let outer = String::from("a long string");
let res;
{
let inner = String::from("short");
res = longest(&outer, &inner);
}
println!("{res}");
}
This time the result is stored in a variable of the outer scope.
{
let inner = String::from("short");
res = longest(&outer, &inner);
}
println!("{res}");
At run time res would point to outer, which is
still alive. The compiler does not reason about values, however. According
to the signature, the result may borrow from inner, which is
dropped at the end of the block. The program is rejected.
$ cargo run -q
error[E0597]: `inner` does not live long enough
--> src/main.rs:11:31
|
10 | let inner = String::from("short");
| ----- binding `inner` declared here
11 | res = longest(&outer, &inner);
| ^^^^^^ borrowed value does not live long enough
12 | }
| - `inner` dropped here while still borrowed
13 |
14 | println!("{res}");
| --- borrow later used here
For more information about this error, try `rustc --explain E0597`.
Unrelated parameters
Only parameters that the result can borrow from need to share its lifetime.
fn first_field<'a>(line: &'a str, sep: &str) -> &'a str {
line.split(sep).next().unwrap_or("")
}
fn main() {
let line = String::from("John Doe;gardener;34");
let name;
{
let sep = String::from(";");
name = first_field(&line, &sep);
}
println!("{name}");
}
The first_field function returns the first field of a line
split by a separator.
fn first_field<'a>(line: &'a str, sep: &str) -> &'a str {
The result is a part of line, never of sep. The
signature ties the result only to line; sep gets
its own independent lifetime.
{
let sep = String::from(";");
name = first_field(&line, &sep);
}
println!("{name}");
Thanks to the precise signature, the separator may be dropped while the
result is still in use. With 'a on both parameters, this code
would fail like the previous example.
$ cargo run -q John Doe
Lifetime elision
Many functions take and return references without any annotations. The compiler fills in the lifetimes using three elision rules.
fn first_word(s: &str) -> &str {
s.split_whitespace().next().unwrap_or("")
}
fn count_char(s: &str, c: char) -> usize {
s.chars().filter(|&x| x == c).count()
}
struct Document {
title: String,
body: String,
}
impl Document {
fn title(&self) -> &str {
&self.title
}
fn find(&self, word: &str) -> Option<&str> {
self.body.split_whitespace().find(|w| w.contains(word))
}
}
fn main() {
let text = "an old falcon";
println!("{}", first_word(text));
println!("{}", count_char(text, 'o'));
let doc = Document {
title: String::from("Notes"),
body: String::from("The falcon is a bird of prey"),
};
println!("{}", doc.title());
println!("{:?}", doc.find("pre"));
}
None of the functions and methods has lifetime annotations.
- Each reference parameter gets its own lifetime parameter.
- If there is exactly one input lifetime, it is assigned to all output references.
- If one of the parameters is
&selfor&mut self, its lifetime is assigned to all output references.
fn first_word(s: &str) -> &str {
By rules 1 and 2, this signature is the same as the explicit version:
fn first_word<'a>(s: &'a str) -> &'a str {
Writing the lifetimes out is allowed but unnecessary; Clippy warns about it
with the needless_lifetimes lint.
fn count_char(s: &str, c: char) -> usize {
A function that returns no reference never needs lifetime annotations.
fn find(&self, word: &str) -> Option<&str> {
The method has two reference parameters, so rule 2 does not apply. Rule 3
does: the result borrows from self, which is correct because
the found word is a part of the document's body. The longest
function needed annotations because none of the rules applied to it.
$ cargo run -q
an
2
Notes
Some("prey")
Structs holding references
A struct that stores a reference needs a lifetime parameter.
struct Excerpt<'a> {
part: &'a str,
}
impl<'a> Excerpt<'a> {
fn new(text: &'a str) -> Self {
let part = text.split('.').next().unwrap_or(text);
Excerpt { part }
}
fn words(&self) -> usize {
self.part.split_whitespace().count()
}
fn part(&self) -> &'a str {
self.part
}
}
fn main() {
let novel = String::from("Call me Ishmael. Some years ago, never mind how long.");
let first;
{
let ex = Excerpt::new(&novel);
println!("excerpt: {}", ex.part);
println!("words: {}", ex.words());
first = ex.part();
}
println!("still valid: {first}");
}
The Excerpt struct keeps a part of a longer text without
copying it.
struct Excerpt<'a> {
part: &'a str,
}
The annotation means that an Excerpt cannot outlive the string
its part field refers to.
impl<'a> Excerpt<'a> {
fn new(text: &'a str) -> Self {
The lifetime is declared after impl and used in the type name.
The constructor ties the excerpt to the text it is created from.
fn words(&self) -> usize {
self.part.split_whitespace().count()
}
fn part(&self) -> &'a str {
self.part
}
The part method returns &'a str instead of
relying on elision. By rule 3, an elided return type would be tied to
&self, the borrow of the excerpt. With 'a, the
result is tied to the original text.
let first;
{
let ex = Excerpt::new(&novel);
...
first = ex.part();
}
println!("still valid: {first}");
Therefore the slice remains usable after the excerpt itself is gone. It
borrows novel, which is still alive.
$ cargo run -q excerpt: Call me Ishmael words: 3 still valid: Call me Ishmael
Two lifetime parameters
Sometimes a struct holds references with different lifetimes.
struct Search<'t, 'k> {
text: &'t str,
keyword: &'k str,
}
impl<'t> Search<'t, '_> {
fn find(&self) -> Option<&'t str> {
self.text
.split_whitespace()
.find(|w| w.starts_with(self.keyword))
}
}
fn main() {
let text = String::from("the falcon flew over the field");
let found;
{
let keyword = String::from("fl");
let s = Search { text: &text, keyword: &keyword };
found = s.find();
}
println!("{found:?}");
}
The Search struct looks for a word in a text that starts with a
keyword.
struct Search<'t, 'k> {
text: &'t str,
keyword: &'k str,
}
The text and the keyword get separate lifetimes. The found word is a part of the text; the keyword is needed only while searching.
impl<'t> Search<'t, '_> {
fn find(&self) -> Option<&'t str> {
The result is tied to 't only. The '_
placeholder says that the keyword lifetime is not referred to in the block.
{
let keyword = String::from("fl");
let s = Search { text: &text, keyword: &keyword };
found = s.find();
}
println!("{found:?}");
The keyword is dropped before the result is printed. With a single lifetime
for both fields, the result would be limited by the short-lived keyword and
the compiler would report that keyword does not live long
enough.
$ cargo run -q
Some("flew")
The 'static lifetime
The 'static lifetime denotes data that can live for the entire
duration of the program.
use std::fmt::Display;
static GREETING: &str = "Hello";
fn level_name(level: u8) -> &'static str {
match level {
0 => "debug",
1 => "info",
2 => "warning",
_ => "error",
}
}
fn show_static<T: Display + 'static>(val: T) {
println!("{val}");
}
fn main() {
let s: &'static str = "a string literal";
println!("{s}");
println!("{GREETING}");
let name = level_name(2);
println!("{name}");
let owned = String::from("owned String");
show_static(owned);
show_static(42);
show_static("literal");
}
String literals are stored in the program binary and have the type
&'static str.
fn level_name(level: u8) -> &'static str {
match level {
0 => "debug",
...
}
}
A function can return a reference to a literal without borrowing from any
parameter. Since there is no input reference, elision cannot supply the
lifetime and we must write it. The 'static lifetime says that
the result does not borrow from anything.
fn show_static<T: Display + 'static>(val: T) {
A T: 'static bound means something different: the type
T contains no references that could expire. Owned types such as
String and i32 satisfy it, even though a particular
String value is dropped at some point. Such bounds appear, for
example, in std::thread::spawn, because a thread may run for
any length of time.
$ cargo run -q a string literal Hello warning owned String 42 literal
'static bounds and local references
A reference to a local variable does not satisfy a 'static
bound.
use std::fmt::Display;
fn show_static<T: Display + 'static>(val: T) {
println!("{val}");
}
fn main() {
let owned = String::from("owned String");
show_static(&owned);
}
The &owned reference is valid only until the end of
main, not for the whole program.
$ cargo run -q
error[E0597]: `owned` does not live long enough
--> src/main.rs:9:17
|
8 | let owned = String::from("owned String");
| ----- binding `owned` declared here
9 | show_static(&owned);
| ------------^^^^^^-
| | |
| | borrowed value does not live long enough
| argument requires that `owned` is borrowed for `'static`
10 | }
| - `owned` dropped here while still borrowed
|
note: requirement that the value outlives `'static` introduced here
--> src/main.rs:3:29
|
3 | fn show_static<T: Display + 'static>(val: T) {
| ^^^^^^^
For more information about this error, try `rustc --explain E0597`.
Lifetimes of iterator items
A common use of lifetimes is an iterator that yields slices of its input.
struct Tokenizer<'a> {
input: &'a str,
}
impl<'a> Iterator for Tokenizer<'a> {
type Item = &'a str;
fn next(&mut self) -> Option<&'a str> {
let s = self.input.trim_start();
if s.is_empty() {
return None;
}
let end = if s.starts_with(|c: char| c.is_ascii_digit()) {
s.find(|c: char| !c.is_ascii_digit()).unwrap_or(s.len())
} else {
s.chars().next().map_or(0, char::len_utf8)
};
let (tok, rest) = s.split_at(end);
self.input = rest;
Some(tok)
}
}
fn main() {
let expr = String::from("12 + 345*(6 - 78)");
let tokens: Vec<&str> = {
let t = Tokenizer { input: &expr };
t.collect()
};
println!("{tokens:?}");
}
The Tokenizer splits an arithmetic expression into numbers and
single-character tokens.
impl<'a> Iterator for Tokenizer<'a> {
type Item = &'a str;
The items borrow from the input string, not from the tokenizer. The
next method takes &mut self, but its return
type uses 'a, so the tokens are independent of that temporary
borrow.
let (tok, rest) = s.split_at(end); self.input = rest; Some(tok)
The split_at method splits the slice into the token and the
remaining input. No string data is copied.
let tokens: Vec<&str> = {
let t = Tokenizer { input: &expr };
t.collect()
};
The tokenizer is dropped at the end of the block, yet the collected tokens
stay valid, because they borrow expr.
$ cargo run -q
["12", "+", "345", "*", "(", "6", "-", "78", ")"]
The anonymous lifetime
The '_ placeholder stands for a lifetime that we do not need to
name.
use std::fmt;
struct Excerpt<'a> {
part: &'a str,
}
impl fmt::Display for Excerpt<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "\"{}...\"", self.part)
}
}
fn evens(vals: &[i32]) -> impl Iterator<Item = &i32> {
vals.iter().filter(|x| *x % 2 == 0)
}
fn excerpt(text: &str) -> Excerpt<'_> {
Excerpt { part: &text[..text.len().min(12)] }
}
fn main() {
let vals = vec![1, 2, 3, 4, 5, 6];
let res: Vec<_> = evens(&vals).collect();
println!("{res:?}");
let text = String::from("It was a bright cold day in April");
println!("{}", excerpt(&text));
}
We implement Display for a struct with a lifetime and write two
functions that return borrowing values.
impl fmt::Display for Excerpt<'_> {
The implementation does not refer to the lifetime, so we use
'_ instead of declaring impl<'a>.
fn excerpt(text: &str) -> Excerpt<'_> {
In a return type, '_ uses the elided lifetime. Writing just
Excerpt compiles as well, but hides the fact that the result
borrows from text; the compiler warns about it with the
mismatched_lifetime_syntaxes lint.
fn evens(vals: &[i32]) -> impl Iterator<Item = &i32> {
The returned iterator borrows the slice. In the 2024 edition an
impl Trait return type automatically captures the lifetimes of
the parameters. Older editions required writing
impl Iterator<Item = &i32> + '_.
$ cargo run -q [2, 4, 6] "It was a bri..."
Non-lexical lifetimes
A borrow lasts until the last use of the reference, not until the end of the enclosing block.
fn main() {
let mut vals = vec![1, 2, 3];
let first = &vals[0];
println!("first: {first}");
vals.push(4);
println!("{vals:?}");
}
We take a reference to the first element and then push a new element.
let first = &vals[0];
println!("first: {first}");
vals.push(4);
The shared borrow ends after first is printed. The mutable
borrow needed by push does not overlap with it, so the program
compiles. This behaviour is called non-lexical lifetimes.
$ cargo run -q first: 1 [1, 2, 3, 4]
Overlapping borrows
If we use the reference after the push, the borrows overlap.
fn main() {
let mut vals = vec![1, 2, 3];
let first = &vals[0];
vals.push(4);
println!("first: {first}");
}
The error protects us from a real bug: push may reallocate the
vector's buffer, and first would then point to freed memory.
$ cargo run -q
error[E0502]: cannot borrow `vals` as mutable because it is also borrowed as immutable
--> src/main.rs:5:5
|
4 | let first = &vals[0];
| ---- immutable borrow occurs here
5 | vals.push(4);
| ^^^^^^^^^^^^ mutable borrow occurs here
6 |
7 | println!("first: {first}");
| ----- immutable borrow later used here
For more information about this error, try `rustc --explain E0502`.
Lifetimes with generic types
Lifetime parameters can be combined with generic type parameters and trait bounds.
use std::fmt::Display;
fn longest_with_note<'a, T>(x: &'a str, y: &'a str, note: T) -> &'a str
where
T: Display,
{
println!("note: {note}");
if x.len() >= y.len() { x } else { y }
}
fn main() {
let a = String::from("sunrise");
let b = String::from("dusk");
let res = longest_with_note(&a, &b, 2026);
println!("{res}");
let res = longest_with_note("left", "right", "comparing literals");
println!("{res}");
}
The function prints a note of any displayable type and returns the longer string.
fn longest_with_note<'a, T>(x: &'a str, y: &'a str, note: T) -> &'a str
where
T: Display,
Lifetime parameters come first in the angle brackets, followed by type parameters. The note is passed by value and does not take part in the lifetime relationship.
$ cargo run -q note: 2026 sunrise note: comparing literals right
Source
The Rust Programming Language: Validating References with Lifetimes
The Rust Reference: Lifetime elision
In this article we have worked with lifetimes in Rust.
Author
List all Rust tutorials.