Lifetimes

On this page

Lifetime parameters describe validity relationships that must hold between borrows; they are not runtime timers. Annotations do not extend the life of data, nor do they allow local variables to outlive the function that returns them; both the implementation and the caller must satisfy the constraints given by the signature.

Same-name parameters express relationships, not identical actual lifetimes

Outer StringInner StringCommon valid regionTwo inputs share a lifetime constraint, which does not extend the shorter inputThe signature of choose allows returning either input; the caller cannot guess a longer validity period for the result based solely on this choice.
fn choose<'a>(left: &'a str, right: &'a str, use_left: bool) -> &'a str {
    if use_left { left } else { right }
}
fn main() {
    let outer = String::from("outer");
    {
        let inner = String::from("inner");
        let selected = choose(&outer, &inner, true);
        assert_eq!(selected, "outer");
    }
}

The two inputs do not need to originally have exactly the same lifetime; the compiler can choose a common region that satisfies both the call and the result usage under the allowed relationships. Since the signature allows returning either input, the caller cannot carry the result past the point where inner becomes invalid, nor can it expand the contract just because use_left was true this time.

If the function always returns left, the output should be associated only with the lifetime of left, avoiding unnecessary constraints on right. Lifetime design is also API precision design.

When only depending on the left input, let the right input be independent

fn left_with_check<'a>(left: &'a str, right: &str) -> &'a str {
    assert!(!right.is_empty());
    left
}
fn main() {
    let outer = String::from("outer");
    let result;
    {
        let temporary = String::from("validation only");
        result = left_with_check(&outer, &temporary);
    }
    assert_eq!(result, "outer");
}

right is only involved in the check during the call and is not the source of the returned reference, so it can expire earlier. If right were also written as &'a str, the caller would be subject to unnecessary common constraints; the compiler checks calls against the public signature and does not peek into the implementation for each call to relax promises.

Which input constrains the result?

Adjust the intervals to expose the result dependency. Hold its use fixed, shorten right, then tie the result only to left. The improvement comes from removing an irrelevant dependency in the signature, not extending an object’s lifetime.

Preparing the visual
Which input constrains the result?

A result tied to both inputs is constrained by their common region; one tied only to left can outlive right.

Elision rules do not rely on guessing the function body

Omitted reference lifetimes in function parameters each receive independent parameters. When there is only one input lifetime, it is used for the elided output lifetime; when a method has a &self or &mut self receiver, the output is default-associated with the receiver's borrow. If it cannot be determined by the rules, it must be expressed explicitly. Elision Rules

struct Excerpt<'s> { part: &'s str }
impl<'s> Excerpt<'s> {
    fn tied_to_self(&self) -> &str { self.part }
    fn tied_to_source(&self) -> &'s str { self.part }
}
fn main() {
    let source = String::from("chapter");
    let result;
    {
        let excerpt = Excerpt { part: &source };
        assert_eq!(excerpt.tied_to_self(), "chapter");
        result = excerpt.tied_to_source();
    }
    assert_eq!(result, "chapter");
}

Both methods return the same field, but the signatures make different promises. The second output depends on the validity of source and can be used after the Excerpt wrapper is destroyed; the first is still constrained by the current &self borrow by default. When a struct holds a reference, lifetime constraints ensure valid usage does not exceed the range allowed by the data; it is not about adding a runtime expiration field to the reference.

source: StringOwns the entire "a,b,c"Fields<'input>Holds a slice of remaining inputReturns &'input strField content borrows from sourcenext_field briefly borrows the parser; earlier fields can remain liveThe borrow of parser state and the borrow of input text are two different relationshipsThis holds because it only advances the slice view and does not rewrite the input; buffered readers requiring coverage need a different contract.

A parser that can borrow multiple results simultaneously

struct Fields<'input> { rest: Option<&'input str> }
impl<'input> Fields<'input> {
    fn new(input: &'input str) -> Self { Self { rest: Some(input) } }
    fn next_field(&mut self) -> Option<&'input str> {
        let remaining = self.rest.take()?;
        match remaining.split_once(',') {
            Some((first, rest)) => {
                self.rest = Some(rest);
                Some(first)
            }
            None => Some(remaining),
        }
    }
}
fn main() {
    let source = String::from("a,,c,");
    let first;
    {
        let mut fields = Fields::new(&source);
        first = fields.next_field().unwrap();
        let second = fields.next_field().unwrap();
        assert_eq!((first, second), ("a", ""));
        assert_eq!(fields.next_field(), Some("c"));
        assert_eq!(fields.next_field(), Some(""));
        assert_eq!(fields.next_field(), None);
    }
    assert_eq!(first, "a");
}

This is just comma-separated fields, not a full CSV with quotes and escaping. Option distinguishes between "there is still a last empty field" and "it has ended," so a trailing comma produces Some("") followed by None. next_field returns &'input str, associating the output with the input, not the current &mut self; even after the parser itself is destroyed, first can still be used as long as source is valid.

If the return type were elided to Option<&str>, the method rules would associate the output with the current self borrow, potentially preventing further calls to next_field while retaining old results. This is not about adding an arbitrary marker to the compiler, but accurately declaring where the result comes from.

&'static T vs T: 'static

&'static T means the borrow is allowed to cover the entire program execution, such as string literals. T: 'static means T does not contain borrows that do not satisfy the 'static constraint; a String owning data can satisfy it, even if that String is dropped on the next line.

fn accepts_static<T: 'static>(value: T) { drop(value); }
fn main() {
    accepts_static(String::from("short lived owner"));
    let literal: &'static str = "literal";
    accepts_static(literal);
}

Moving a short-lived reference does not make it 'static. Box::leak can produce a long-lived borrow when T satisfies the corresponding constraints, but leaking should not be the default fix for ordinary lifetime errors; first consider returning owned values, shortening borrows, or adjusting resource ownership.

Use Cow to express "may borrow or may generate new content"

use std::borrow::Cow;
fn lowercase_ascii(text: &str) -> Cow<'_, str> {
    if text.bytes().any(|b| b.is_ascii_uppercase()) {
        Cow::Owned(text.to_ascii_lowercase())
    } else {
        Cow::Borrowed(text)
    }
}
fn main() {
    assert!(matches!(lowercase_ascii("ready"), Cow::Borrowed(_)));
    assert!(matches!(lowercase_ascii("READY"), Cow::Owned(_)));
    let saved;
    {
        let input = String::from("READY");
        saved = lowercase_ascii(&input).into_owned();
    }
    assert_eq!(saved, "ready");
}

The return type preserves the relationship that it might borrow the input; the caller cannot treat the entire Cow as 'static just because "this string has uppercase letters." into_owned explicitly takes the String: the borrow branch requires copying, while the owned branch directly transfers the existing String. This function only handles ASCII case, not Unicode case folding or identity normalization. Cow

Covariance must specify which parameter

'a: 'b means 'a outlives 'b, i.e., 'a satisfies at least covering 'b. Whether a reference can be shortened also depends on the position of the type parameter:

TypeTo outer 'aTo inner T
&'a TCovariantCovariant
&'a mut TCovariantInvariant
UnsafeCellN/AInvariant

Mutable references are not invariant with respect to their own lifetime; they are invariant with respect to the referenced T. If T is again &'static str, you cannot treat &mut T as a location that can write to any short-lived borrow. Covariance Rules

fn shorter<'long: 'short, 'short>(value: &'long mut i32) -> &'short mut i32 {
    value
}
fn main() {
    let mut number = 1;
    *shorter(&mut number) += 1;
    assert_eq!(number, 2);
}

The risk in the opposite direction is writing: if an &mut &'static str could accept a reference to a short-lived String, the outer location would still be used as 'static later, leading to a dangling reference. This explains why the inner T must be invariant, which is different from temporarily shortening an exclusive borrow.

What would writing a short reference leave behind?

Understand invariance through the write channel, not just the current read. The original slot stores &'static str; weakening its inner type would let a short view write local, while the original slot still promises static afterward. Shortening only the outer borrow leaves the accepted type unchanged.

Preparing the visual
What would writing a short reference leave behind?

&mut T is covariant in its outer lifetime and invariant in T, preventing short references from being stored in static slots.

HRTB: Callbacks must apply to the borrow of each call

fn apply<F>(function: F)
where F: for<'a> Fn(&'a str) -> &'a str,
{
    let local = String::from("  local  ");
    assert_eq!(function(&local), "local");
    assert_eq!(function(" literal "), "literal");
}
fn trim(text: &str) -> &str { text.trim() }
fn main() { apply(trim); }

for<'a> requires the callback to satisfy the interface for any applicable 'a, allowing apply to call it with internal temporary borrows. It is different from an outer caller selecting a specific 'a. The elided form Fn(&str) -> &str can also express this higher-order lifetime relationship.

PhantomData does not automatically validate raw pointers

PhantomData<&'a T> allows a struct without actual reference fields to express logical borrow relationships in type checking, affecting lifetime, covariance, and auto trait inference. However, it does not validate addresses, nor does it guarantee that the pointer comes from that borrow at construction time.

Iterators encapsulating raw pointers still need to establish range and lifetime constraints from valid slices or other entry points, maintaining initialization, alignment, and access rules. Simply adding a marker to the struct and arbitrarily returning &'a T may still be unsound. Related proofs can be found in the unsafe contract.

Choose the corresponding interface fix for lifetime errors

Where the error comes fromRelationship to adjust
Returning a reference to a locally created valueReturn an owned value, or let the caller provide storage
Output constrained by unrelated inputSplit input lifetimes, associate only with actual sources
Parser's old result prevents next callConfirm whether the result depends on external input or overridable internal buffer
Async task requires 'staticTransfer owned data, or use a scoping mechanism that allows short borrows
Callback wants to save internal temporary referenceSave an owned copy, or change to consume only during the callback
Lifetime conversion fails for shared and mutable locationsCheck variance, especially T inside &mut T

for<'a> callbacks can receive internally temporary data from the caller, which precisely means they cannot arbitrarily save this short-lived borrow into a longer-lived container. If you want to retain the data, you can to_owned within the callback; if you only need to count the length or parse into an owned number, return those results instead. Lifetime signatures should be consistent with storage strategies, rather than relying on adding more 'a or 'static to get lucky.