On this page
Lifetimes: annotating the lifespan of references
Why do lifetimes exist?
Lifetimes are the way Rust tracks how long references live. They do not exist at runtime — they are purely information for the compiler.
The borrow checker uses lifetimes to guarantee that no reference outlives the data it points to. Without this verification, we could have dangling pointers.
The problem they solve
// How long does the return value live?
fn longest(x: &str, y: &str) -> &str {
if x.len() >= y.len() { x } else { y }
}This function does not compile because the compiler cannot determine whether the return value is related to x or y. If x and y have different lifetimes, the return value could be a reference to something that was already freed.
Lifetime annotation syntax
Lifetime annotations start with ' followed by a name (typically a single lowercase letter):
&i32 // a reference
&'a i32 // a reference with explicit lifetime 'a
&'a mut i32 // a mutable reference with explicit lifetime 'aAnnotations do not change how long references live — they only describe the relationship between the lifetimes of multiple references.
Lifetimes in functions
// 'a is the shorter of the two lifetimes x and y
// The return value lives at least as long as 'a
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() >= y.len() { x } else { y }
}
fn main() {
let string1 = String::from("long string is long");
// Case 1: Both live the same amount of time
let string2 = String::from("xyz");
let result = longest(string1.as_str(), string2.as_str());
println!("Longest: {result}");
// Case 2: One lives less than the other
{
let string3 = String::from("another string");
// Use the result inside the scope of string3
let r = longest(&string1, &string3);
println!("Result in scope: {r}");
}
}Lifetime elision rules
In practice, you often don't need to write lifetimes manually. The compiler applies three elision rules to infer them automatically:
Rule 1: Each reference parameter gets its own lifetime.
// What you write:
fn first(s: &str) -> &str
// What the compiler infers:
fn first<'a>(s: &'a str) -> &'a strRule 2: If there is exactly one input reference parameter, its lifetime is assigned to all output references.
// What you write:
fn first_word(s: &str) -> &str
// What the compiler infers:
fn first_word<'a>(s: &'a str) -> &'a strRule 3: If one of the parameters is &self or &mut self, its lifetime is assigned to all output references.
struct Text { content: String }
impl Text {
// What you write:
fn get_slice(&self) -> &str
// What the compiler infers:
// fn get_slice<'a>(&'a self) -> &'a str
}Structs with lifetimes
When a struct contains references, it needs lifetime annotations:
// The struct cannot outlive the reference it contains
struct Fragment<'a> {
text: &'a str,
start: usize,
end: usize,
}
impl<'a> Fragment<'a> {
fn new(text: &'a str, start: usize, end: usize) -> Self {
Fragment { text, start, end }
}
fn content(&self) -> &str {
&self.text[self.start..self.end]
}
fn length(&self) -> usize {
self.end - self.start
}
}
fn main() {
let text = String::from("Rust is a modern systems language");
let fragment = Fragment::new(&text, 0, 4);
println!("Fragment: '{}'", fragment.content()); // "Rust"
println!("Length: {}", fragment.length());
// Fragment cannot outlive text
println!("Full text: {text}");
}Lifetime bounds: lifetime constraints
You can add lifetime constraints to generic types:
use std::fmt::Display;
// T must live at least as long as 'a
fn show_reference<'a, T>(value: &'a T)
where
T: Display + 'a,
{
println!("Value: {value}");
}
// A reference to T must live 'a, and T must implement Display
fn find_longest<'a, T>(
list: &'a [T],
predicate: impl Fn(&T) -> bool,
) -> Option<&'a T>
where
T: Display,
{
list.iter().find(|item| predicate(item))
}
fn main() {
let numbers = vec![1, 5, 2, 8, 3, 7];
if let Some(found) = find_longest(&numbers, |&n| n > 5) {
println!("Found: {found}");
}
let words = vec!["hello", "world", "rust"];
show_reference(&words[0]);
}The `'static` lifetime
'static is a special lifetime meaning "lives for the entire duration of the program":
fn main() {
// String literals are 'static — they live in the binary
let greeting: &'static str = "Hello, world";
// Constants are also 'static
static CONSTANT: &str = "I am static";
println!("{greeting}");
println!("{CONSTANT}");
}
// This function can return a 'static reference
fn get_message(code: u32) -> &'static str {
match code {
200 => "OK",
404 => "Not found",
500 => "Internal server error",
_ => "Unknown code",
}
}'static also appears in trait object bounds: Box<dyn Error + 'static> or Box<dyn Error + Send + 'static>. This indicates the error type does not contain references with lifetimes shorter than 'static.
Lifetimes in practice
In real Rust code, you rarely need to write lifetimes manually because:
- Elision rules cover the most common cases
- Structs that own their data (using
Stringinstead of&str,Vecinstead of&[]) don't need annotations
// Without lifetimes — owns its data
struct User {
name: String, // String, not &str
age: u32,
}
// With lifetimes — references external data
struct UserView<'a> {
name: &'a str, // &str, needs lifetime
age: u32,
}The general rule: prefer types that own their data in structs. Use references only when performance demands it and you are prepared to manage lifetimes.
With ownership, borrowing, and lifetimes understood, you have the knowledge of Rust's unique type system. The next lesson introduces structs — the primary way to create custom data types.
// Without annotation: the compiler doesn't know how long the return lives
// fn longest(x: &str, y: &str) -> &str { ... } // Error
// With lifetime annotation 'a
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() >= y.len() { x } else { y }
}
// Struct containing a reference needs a lifetime
struct Excerpt<'a> {
part: &'a str,
}
impl<'a> Excerpt<'a> {
fn display(&self) {
println!("Excerpt: {}", self.part);
}
}
fn main() {
let string1 = String::from("long string");
let result;
{
let string2 = String::from("xyz");
result = longest(&string1, &string2);
println!("The longest is: {result}");
}
// Struct with reference — the text must outlive the struct
let novel = String::from("Call me Ishmael. Some years ago...");
let first_sentence;
{
let i = novel.find('.').unwrap_or(novel.len());
first_sentence = Excerpt { part: &novel[..i] };
first_sentence.display();
}
}
Sign in to track your progress