Lifetimes

Higher-Ranked Lifetimes with for<'a>

While Rust automatically elides for<'a> in simple Fn(&T) bounds, explicit for<'a> syntax is required when writing custom traits with lifetime parameters, returning multiple references with tied lifetimes, or specifying higher-ranked function pointer types (for<'a> fn(&'a T) -> &'a U).

Where Explicit for<'a> Is Used

  1. Custom traits parameterized by a lifetime: T: for<'a> MyTrait<'a> (unlike Fn(...), custom traits do not implicitly introduce a higher-ranked for<'a> scope without explicit syntax or '_).
  2. Closures or function pointers with shared input lifetimes: for<'a> fn(&'a str, &'a str) -> &'a str (where elision would otherwise assign two distinct lifetimes 'a and 'b).
  3. Trait objects over borrowed inputs: Box<dyn for<'a> Processor<'a>>.

Custom Traits with for<'a>

Suppose you define a BorrowingParser<'a> trait that parses a borrowed slice &'a str and returns a slice or view tied to 'a. To write a function that feeds temporary stack buffers into any parser, you bound the parser with for<'a> BorrowingParser<'a>:

trait KeyExtractor<'a> {
    fn extract_key(&self, line: &'a str) -> Option<&'a str>;
}

struct ColonExtractor;

impl<'a> KeyExtractor<'a> for ColonExtractor {
    fn extract_key(&self, line: &'a str) -> Option<&'a str> {
        line.split_once(':').map(|(k, _)| k.trim())
    }
}

// Requires `E` to work for EVERY lifetime `'a`, so we can pass lines
// created inside the loop body!
fn parse_lines<E>(extractor: &E)
where
    E: for<'a> KeyExtractor<'a>,
{
    for i in 1..=2 {
        let dynamic_line = format!("header_{i}: value_{i}");
        if let Some(key) = extractor.extract_key(&dynamic_line) {
            println!("Extracted key: {key}");
        }
    }
}

fn main() {
    let extractor = ColonExtractor;
    parse_lines(&extractor);
}
Extracted key: header_1
Extracted key: header_2

Explicit for<'a> in Function Pointers and Closures

When a function pointer or closure takes two references and can return either of them, elision (fn(&str, &str) -> &str) is rejected because there are two input lifetimes. Writing for<'a> fn(&'a str, &'a str) -> &'a str binds both inputs and the output to the same higher-ranked lifetime 'a:

type SelectorFn = for<'a> fn(&'a str, &'a str) -> &'a str;

fn longer<'a>(a: &'a str, b: &'a str) -> &'a str {
    if a.len() >= b.len() { a } else { b }
}

fn run_selector(select: SelectorFn) {
    let s1 = String::from("short");
    let s2 = String::from("much longer string");
    println!("Selected: '{}'", select(&s1, &s2));
}

fn main() {
    run_selector(longer);
}
Selected: 'much longer string'

Guiding Type Inference for Closures

Sometimes passing a closure literal |x| x to a function with a for<'a> bound causes a type inference error ("implementation of FnOnce is not general enough") if the compiler infers a single concrete lifetime instead of a higher-ranked one. Annotating the closure's reference parameter (|x: &str| x) resolves the ambiguity immediately.