Syntax Cache
BlogMethodFeaturesHow It WorksBuild a Game
  1. Home
  2. Cheat Sheets
  3. Rust
  4. Rust Lifetimes Cheat Sheet
RustCheat Sheet

Rust Lifetimes Cheat Sheet

Quick-reference for lifetime annotations, elision rules, and common patterns. Each section includes copy-ready snippets with inline output comments.

On this page
  1. 1Lifetime Annotation Syntax
  2. 2Lifetime Elision Rules
  3. 3Lifetimes in Structs
  4. 4'static Lifetime
  5. 5Non-Lexical Lifetimes (NLL)
  6. 6Lifetime Bounds
  7. 7Common Lifetime Patterns
  8. 8Common Lifetime Errors
  9. 9Lifetimes with Traits
Lifetime Annotation SyntaxLifetime Elision RulesLifetimes in Structs'static LifetimeNon-Lexical Lifetimes (NLL)Lifetime BoundsCommon Lifetime PatternsCommon Lifetime ErrorsLifetimes with Traits

Lifetime Annotation Syntax

Lifetime annotations describe how long references are valid relative to each other. They do not change how long data lives.

Basic annotation on references
&i32        // a reference (lifetime inferred)
&'a i32     // a reference with explicit lifetime 'a
&'a mut i32 // a mutable reference with explicit lifetime 'a
In function signatures
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() { x } else { y }
}

The annotation says: the return value lives at least as long as the shorter of x and y.

Multiple lifetime parameters
fn first_of<'a, 'b>(x: &'a str, _y: &'b str) -> &'a str {
    x  // return borrows from x only
}

Lifetime Elision Rules

Three rules let the compiler infer lifetimes automatically. You only annotate when these rules are not enough.

Rule 1: each parameter gets its own lifetime
// The compiler sees:
fn foo(x: &str, y: &str)
// as:
fn foo<'a, 'b>(x: &'a str, y: &'b str)
Rule 2: one input lifetime -> assigned to all outputs
// No annotation needed:
fn first_word(s: &str) -> &str {
    &s[..s.find(' ').unwrap_or(s.len())]
}
// Compiler infers: fn first_word<'a>(s: &'a str) -> &'a str
Rule 3: &self lifetime -> assigned to all outputs
struct Parser { input: String }

impl Parser {
    // No annotation needed: &self's lifetime applies to return
    fn first_token(&self) -> &str {
        &self.input[..3]
    }
}
When elision fails: two inputs, one output
// ERROR (E0106): compiler cannot infer which input the return borrows from
// fn longest(x: &str, y: &str) -> &str { ... }

// FIX: annotate explicitly
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() { x } else { y }
}

Lifetimes in Structs

A struct holding a reference needs a lifetime parameter. The struct cannot outlive its borrowed data.

Struct with one reference field
struct Excerpt<'a> {
    part: &'a str,
}

let novel = String::from("Call me Ishmael. Some years ago...");
let excerpt = Excerpt { part: &novel[..16] };
println!("{}", excerpt.part);  // => "Call me Ishmael."
// excerpt cannot outlive novel
Struct with multiple reference fields
struct Pair<'a, 'b> {
    first: &'a str,
    second: &'b str,
}

Use separate lifetimes when references come from independent sources.

impl block with lifetimes
impl<'a> Excerpt<'a> {
    fn part(&self) -> &str {
        self.part  // elision rule 3: returns &self's lifetime
    }

    fn announce(&self, announcement: &str) -> &str {
        println!("Attention: {announcement}");
        self.part  // elision rule 3 applies
    }
}

'static Lifetime

Two meanings: &'static T is a reference valid for the entire program. T: 'static means T contains no short-lived borrows.

String literals are &'static str
let s: &'static str = "hello world";
// The string data is embedded in the binary -- lives forever
T: 'static means self-contained
// String satisfies 'static because it owns its data
fn send_to_thread<T: Send + 'static>(val: T) {
    std::thread::spawn(move || {
        println!("{val:?}");
    });
}

send_to_thread(String::from("hello"));  // OK: String is 'static
// send_to_thread(&local_string);       // ERROR: &str is not 'static

T: 'static does not mean "lives forever". It means the type does not borrow anything short-lived.

Lazy static values
// const strings are &'static str
const APP_NAME: &str = "myapp";

// Static mutable data requires unsafe or sync primitives
use std::sync::LazyLock;
static CONFIG: LazyLock<String> = LazyLock::new(|| {
    std::fs::read_to_string("config.toml").unwrap_or_default()
});

Non-Lexical Lifetimes (NLL)

Borrows end at their last use, not at the closing }. This allows more code to compile since Rust 2018.

Borrow ends at last use
let mut v = vec![1, 2, 3];
let first = &v[0];
println!("{first}");  // last use of first -- borrow ends here
v.push(4);            // OK: no active borrow
Without NLL this would fail
let mut data = String::from("hello");
let r = &data;
println!("{r}");       // NLL: borrow of data ends here
data.push_str(" world");  // OK in Rust 2018+
println!("{data}");
NLL fix pattern: move the last use up
let mut map = std::collections::HashMap::new();
map.insert("key", vec![1, 2, 3]);

// If you get E0502, move the immutable use before the mutation:
let len = map.get("key").map_or(0, |v| v.len());  // borrow ends
map.entry("key").or_default().push(4);             // mutation OK

Lifetime Bounds

Express relationships between lifetimes. 'a: 'b means 'a outlives 'b. T: 'a means T contains no references shorter than 'a.

'a: 'b (outlives)
fn first<'a, 'b>(x: &'a str, _y: &'b str) -> &'a str
where
    'a: 'b,  // 'a lives at least as long as 'b
{
    x
}
T: 'a (type contains no short-lived refs)
struct Ref<'a, T: 'a> {
    data: &'a T,
}
// T must not contain references shorter than 'a
T: 'static bound on thread::spawn
// thread::spawn requires 'static because the thread may outlive the caller
std::thread::spawn(move || {
    // closure must own all its data -- no borrowed references
});

Common Lifetime Patterns

Frequently occurring patterns for working with lifetimes in real code.

Return reference from one of two parameters
fn longer<'a>(a: &'a str, b: &'a str) -> &'a str {
    if a.len() >= b.len() { a } else { b }
}
Return reference from only one parameter
fn first<'a>(a: &'a str, _b: &str) -> &'a str {
    a  // output lifetime tied to first parameter only
}
Struct method returning a reference
struct Config {
    name: String,
}

impl Config {
    fn name(&self) -> &str {
        &self.name  // elision: lifetime tied to &self
    }
}
Closure capturing a reference
fn apply<'a>(s: &'a str, f: impl Fn(&'a str) -> &'a str) -> &'a str {
    f(s)
}

Common Lifetime Errors

Quick fixes for the most frequent lifetime-related compiler errors.

E0106: missing lifetime specifier
// ERROR: two inputs, one output -- ambiguous
// fn pick(a: &str, b: &str) -> &str { a }

// FIX: annotate which input the output borrows from
fn pick<'a>(a: &'a str, _b: &str) -> &'a str { a }
E0515: cannot return reference to local
// ERROR: local String is dropped at function end
// fn make() -> &str {
//     let s = String::from("hi");
//     &s
// }

// FIX: return owned value
fn make() -> String {
    String::from("hi")
}
E0597: borrowed value does not live long enough
// ERROR: x is dropped before r is used
// let r;
// { let x = 5; r = &x; }  // x dropped here
// println!("{r}");

// FIX: move x to the same or wider scope
let x = 5;
let r = &x;
println!("{r}");
E0716: temporary value dropped while borrowed
// ERROR: String temporary dropped at semicolon
// let s: &str = &String::from("hello");

// FIX: bind the temporary first
let owned = String::from("hello");
let s: &str = &owned;

Lifetimes with Traits

Trait objects have implicit 'static lifetime. Override with explicit lifetime bounds.

Default trait object lifetime is 'static
// These are equivalent:
fn takes_box(b: Box<dyn std::fmt::Display>)
fn takes_box(b: Box<dyn std::fmt::Display + 'static>)
Explicit lifetime on trait objects
fn print_it<'a>(item: &'a dyn std::fmt::Display) {
    println!("{item}");
}

// Or with Box:
fn store_it<'a>(item: Box<dyn std::fmt::Display + 'a>) {
    println!("{item}");
}
Higher-ranked trait bounds (HRTB)
// for<'a> means: "for any lifetime 'a"
fn apply(f: impl for<'a> Fn(&'a str) -> &'a str, s: &str) -> String {
    f(s).to_string()
}

HRTB is advanced. You'll see it in closure bounds and framework APIs.

Learn Rust in Depth
Rust Ownership & Borrowing Practice →Rust Traits & Generics Practice →
Warm-up1 / 2

Can you write this from memory?

Given `let s = String::from("hello");`, create an immutable reference `r` to `s`.

See Also
Ownership & Borrowing →String Types →

Start Practicing Rust

Free daily exercises with spaced repetition. No credit card required.

← Back to Rust Syntax Practice
Syntax Cache

Build syntax muscle memory with spaced repetition.

Product

  • Pricing
  • Our Method
  • Daily Practice
  • Design Patterns
  • Interview Prep

Resources

  • Blog
  • Compare
  • Cheat Sheets
  • Vibe Coding
  • Muscle Memory

Languages

  • Python
  • JavaScript
  • TypeScript
  • Rust
  • SQL
  • GDScript

Legal

  • Terms
  • Privacy
  • Contact

© 2026 Syntax Cache

Cancel anytime in 2 clicks. Keep access until the end of your billing period.

No refunds for partial billing periods.