Lifetimes

Higher-Ranked Trait Bounds

A Higher-Ranked Trait Bound (HRTB), written for<'a> Trait<&'a T>, states that a type implements a trait for every possible lifetime 'a, rather than for one fixed lifetime chosen by the caller before calling the function.

Why Ordinary Generic Lifetimes Fail for Closures Borrowing Local Data

Suppose a function call_on_local creates a temporary String inside its body and passes a &str reference to a caller-supplied closure F.

If we wrote fn call_on_local<'a, F: Fn(&'a str)>(f: F), the caller would choose 'a outside call_on_local. Because every lifetime 'a outside the function is strictly longer than a local variable created inside the function, the borrow of the local variable could never satisfy 'a:

// ❌ does not compile
fn call_on_local<'a, F>(f: F)
where
    F: Fn(&'a str),
{
    let local = String::from("created inside call_on_local");
    f(&local); // Error: `local` does not live as long as caller-chosen `'a`!
}

fn main() {}
error[E0597]: `local` does not live long enough
 --> src/main.rs:7:7
  |
2 | fn call_on_local<'a, F>(f: F)
  |                  -- lifetime `'a` defined here
...
6 |     let local = String::from("created inside call_on_local");
  |         ----- binding `local` declared here
7 |     f(&local); // Error: `local` does not live as long as caller-chosen `'a`!
  |     --^^^^^^-
  |     | |
  |     | borrowed value does not live long enough
  |     argument requires that `local` is borrowed for `'a`
8 | }
  | - `local` dropped here while still borrowed

Solving the Problem with for<'a> (HRTB)

Instead of fixing a single lifetime 'a on the outer function, we bound F with for<'a> Fn(&'a str). This means F can accept a string slice of any lifetime 'a—including a short lifetime local to call_on_local's stack frame:

fn call_on_local<F>(f: F)
where
    F: for<'a> Fn(&'a str) -> usize,
{
    let local = String::from("created inside call_on_local");
    let count = f(&local);
    println!("Character count of local string: {count}");
}

fn main() {
    call_on_local(|s| s.len());
}
Character count of local string: 28

Implicit Elision of HRTBs in Fn Trait Bounds

Because higher-ranked lifetimes are almost always what you want when writing Fn, FnMut, or FnOnce bounds that take references, Rust applies lifetime elision rules directly inside Fn(...) trait bounds:

Elided Trait Bound Expanded Higher-Ranked Form
F: Fn(&str) F: for<'a> Fn(&'a str)
F: Fn(&str) -> &str F: for<'a> Fn(&'a str) -> &'a str
F: Fn(&str, &str) F: for<'a, 'b> Fn(&'a str, &'b str)
// `F: Fn(&str) -> &str` automatically desugars to `F: for<'a> Fn(&'a str) -> &'a str`:
fn trim_two_buffers<F>(f: F)
where
    F: Fn(&str) -> &str,
{
    let first = String::from("   alpha   ");
    let second = String::from("   beta   ");

    // `f` is called with two completely independent local borrow lifetimes!
    println!("1: '{}'", f(&first));
    println!("2: '{}'", f(&second));
}

fn main() {
    trim_two_buffers(|s| s.trim());
}
1: 'alpha'
2: 'beta'