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 enoughfn 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?