🎓 A1Learner
🔍

Rust Higher-Ranked Trait Bounds

A higher-ranked trait bound is a bound that must hold for every lifetime. The bound is written for<'a>, read "for all lifetimes 'a".

The promise in one line

demand will call f with references that demand itself creates, so f must accept any lifetime demand happens to pick:

fn demand<F>(f: F)
where
    F: for<'a> Fn(&'a str) -> &'a str,
{
    // f is called here, with lifetimes chosen inside demand
}

One callable, two lifetimes in a row

echo is an ordinary fn, and a fn item already promises every lifetime. So demand can call it first with a literal. Then it can call it with a borrow of a local String. Two different lifetimes, one after the other:

fn demand<F>(f: F)
where
    F: for<'a> Fn(&'a str) -> &'a str,
{
    let local = String::from("borrowed words");
    println!("first = {}", f("literal"));
    println!("second = {}", f(&local));
}
fn echo(s: &str) -> &str { s }
// called as: demand(echo);

A callable tied to one lifetime fails

This closure accepts only &'static str. demand may hand it a short-lived borrow. So the promise "for every 'a" is not met. The compiler says the Fn implementation is not general enough:

let f = |s: &'static str| -> &str { s };
demand(f); // error: implementation of `Fn` is not general enough

fn pointers spell it out

Writing the for<> in a type alias shows what fn(&str) -> &str has meant all along. The alias accepts a fresh lifetime at every call:

type AnyRef = for<'a> fn(&'a str) -> &'a str;
fn identity(s: &str) -> &str { s }
let f: AnyRef = identity;
let local = String::from("short lived");
println!("one = {}", f("stays forever"));
println!("two = {}", f(&local));

Example

struct Wrapper<F> {
    data: (u8, u16),
    func: F,
}
impl<F> Wrapper<F>
where
    F: for<'a> Fn(&'a (u8, u16)) -> &'a u8,
{
    fn call(&self) -> &u8 {
        (self.func)(&self.data)
    }
}
fn first(data: &(u8, u16)) -> &u8 {
    &data.0
}
fn echo(s: &str) -> &str { s }
fn main() {
    let w = Wrapper { data: (7, 9), func: first };
    println!("first = {}", w.call());
    let local = String::from("borrowed words");
    println!("{} | {}", echo("literal"), echo(&local));
}

Key points

  • Write for<'a> to bind the lifetime inside the promise itself.
  • F: for<'a> Fn(&'a str) -> &'a str means F works for every choice of 'a.
  • Each call may pick a different 'a.
  • The same callable can take a &'static literal in one call.
  • It can take a borrow of a local String in the next.
  • A plain fn pointer type hides a for<>: fn(&str) -> &str means for<'a> fn(&'a str) -> &'a str
  • Leave the for<> off, with the lifetime named outside.
  • Then it is chosen once by the caller of your function.
  • The callable is tied to that one lifetime for its whole life.
  • A closure saved in a variable may have its lifetimes fixed early by inference.
  • Write the closure at the call site when a for<> bound must be met.
  • Or pass a fn.

In this example, Wrapper stores a pair of numbers and a callable. The bound is higher-ranked because no lifetime can be named when Wrapper is built: the reference to self.data only exists inside call, and its lifetime is whatever &self has at that moment. first returns a reference to the first number of the pair, so call prints first = 7. Below, echo is used the same way at two lifetimes in one line. It runs once on a literal. It runs once on a borrow of the local String. It prints literal | borrowed words. One callable, a fresh lifetime at every call — that is the whole promise for<'a> makes.

Note

for<> also appears inside types: dyn for<'a> Fn(&'a str) -> &'a str is the same promise as a trait object.

Most code meets higher-ranked bounds only through Fn bounds and fn pointers.

Try it Yourself

Edit the code, then press Run.

Running is simulated · Rust Playground

Exercise

What does F: for<'a> Fn(&'a str) -> &'a str promise that a bound with one outer named 'a does not?