How to Use Higher-Ranked Trait Bounds (for<'a>) in Rust

Use `for<'a>` in trait bounds to ensure a type implements a trait for all possible lifetimes.

When a closure needs to handle any lifetime

You're writing a helper function that accepts a closure to transform data. The closure takes a string slice and returns a string slice. You test it with a static string. It works. You test it with a local variable. The compiler screams. The error mentions lifetimes, but your closure looks fine. It works with any string you throw at it. The problem isn't your closure. The problem is how you told the compiler about the closure.

This is the classic Higher-Ranked Trait Bound scenario. You need to tell Rust that the closure works for every possible lifetime, not just one specific lifetime chosen by the caller. The syntax for this is for<'a>. It looks weird, but it's the key to writing truly generic functions that accept closures borrowing from their arguments.

The universal quantifier

Rust's type system usually binds lifetimes to specific scopes. When you write a generic function with a lifetime parameter like <'a>, the caller chooses that lifetime. The function must respect that choice.

Higher-Ranked Trait Bounds flip the script. for<'a> is a universal quantifier. It means "for all lifetimes 'a". When you write F: for<'a> Fn(&'a str) -> &'a str, you are not asking the caller to pick a lifetime. You are demanding that the closure F works with whatever lifetime the function body hands it.

Think of a vending machine. A normal trait bound is like a machine that only accepts quarters minted in 2023. You have to bring a 2023 quarter. An HRTB is a machine that accepts any coin you insert, regardless of the year. The machine doesn't care about the specific year. It just needs to handle whatever you give it.

The lifetime isn't a variable you solve for. It's a quantifier you assert.

Minimal example

Here is a function that applies a closure to a reference and prints the result. Without for<'a>, the compiler cannot verify that the closure handles the lifetime of the local variable inside the function.

/// Applies a closure to a reference and prints the result.
/// The closure must work for any lifetime of the input.
fn apply_and_print<F>(value: &str, f: F)
where
    // for<'a> ensures f works with the lifetime of `value`,
    // not some fixed lifetime chosen by the caller.
    F: for<'a> Fn(&'a str) -> &'a str,
{
    let result = f(value);
    println!("{}", result);
}

fn main() {
    let text = String::from("hello");
    
    // This closure works for any lifetime because it just returns the input.
    let identity = |s: &str| -> &str { s };
    
    // The compiler accepts this because identity satisfies for<'a>.
    apply_and_print(&text, identity);
}

Write for<'a> and watch the error vanish.

What the compiler sees

When you call apply_and_print, you pass a closure and a string. Inside the function, value has a lifetime tied to the caller's data. When you call f(value), you are passing a reference with that specific lifetime.

If the bound were just F: Fn(&str) -> &str, the compiler might try to infer a single lifetime for the function signature. It gets confused because the closure needs to handle the lifetime of value, which is local to the call. The compiler rejects this with E0495 (cannot infer an appropriate lifetime for auto-borrow due to conflicting requirements) or a mismatched types error.

With for<'a>, the compiler knows the closure is universal. It substitutes the lifetime of value into 'a and checks if the closure works. Since identity just returns its input, it works for any lifetime. The check passes.

The closure adapts to the data, not the other way around.

Real-world usage

HRTBs appear often in libraries that wrap closures or implement generic algorithms. A common pattern is a struct that holds a closure and calls it with data of varying lifetimes.

/// A handler that transforms strings using a stored closure.
struct Transformer<F> {
    // The closure must handle any lifetime because execute()
    // might be called with different data over time.
    transform: F,
}

impl<F> Transformer<F>
where
    // The closure works for all lifetimes, allowing flexible usage.
    F: for<'a> Fn(&'a str) -> &'a str,
{
    fn new(transform: F) -> Self {
        Self { transform }
    }

    /// Runs the transformation on the provided input.
    fn execute(&self, input: &str) -> &str {
        (self.transform)(input)
    }
}

fn main() {
    let transformer = Transformer::new(|s| s.trim());

    let local = "  hello  ";
    let result = transformer.execute(local);
    println!("{}", result);

    let static_str = "  world  ";
    let result2 = transformer.execute(static_str);
    println!("{}", result2);
}

HRTBs let you write truly generic helpers without boxing or cloning.

Pitfalls and errors

HRTBs are powerful, but they enforce strict rules. The most common mistake is writing a closure that captures a reference and returns it, violating the lifetime link.

fn bad_example<F>(f: F)
where
    F: for<'a> Fn(&'a str) -> &'a str,
{
    let captured = String::from("captured");
    
    // This closure captures `captured` and returns a reference to it.
    // The return type is tied to `captured`, not the argument.
    let f = |_: &str| -> &str { &captured };
    
    // This fails. The bound requires the output to live as long as the input.
    // The compiler rejects this with a lifetime mismatch error.
    // f("test"); 
}

The bound for<'a> Fn(&'a str) -> &'a str promises that the output reference lives at least as long as the input reference. If the closure returns a reference to captured state, that promise breaks. The compiler will reject the code with a lifetime error, often E0308 (mismatched types) or a detailed lifetime explanation.

Another trap is syntax placement. for<'a> must come before the trait. You write for<'a> Fn(...), not Fn(for<'a> ...). The quantifier binds the lifetime for the entire trait bound.

If the closure captures state, check the lifetimes. The bound promises the output lives with the input, and the compiler will enforce it.

Convention asides

When you see Rc::clone(&data) in the wild, you might notice the explicit &data. Both Rc::clone(&data) and data.clone() compile and do the same thing. The convention is the explicit form because data.clone() looks like a deep clone to readers familiar with other languages, but it only bumps the reference count.

For HRTBs, the convention is similar. Use for<'a> explicitly when the compiler complains or when the lifetime relationship is complex. In simple cases, Rust's lifetime elision rules often infer HRTBs automatically. For example, Fn(&str) often desugars to for<'a> Fn(&'a str) for the argument. But when the closure returns a reference, elision can fail. Explicit for<'a> is the safe choice. It documents your intent and prevents subtle errors.

Also, keep unsafe blocks small. The community calls this the "minimum unsafe surface" rule. HRTBs are safe, but they often appear in code that eventually touches unsafe abstractions. Write clear bounds to keep the safe interface robust.

Decision matrix

Use for<'a> when a closure takes a reference and returns a reference that depends on the input. Use for<'a> when the compiler rejects a generic function with a "cannot infer lifetime" error involving closures. Reach for dyn for<'a> Fn when you need a trait object that can be called with references of varying lifetimes. Stick to Fn(&str) without for<'a> when the closure only consumes values or returns owned types; the extra syntax adds noise without value.

HRTBs are the bridge between generic flexibility and lifetime safety. Use them when the compiler needs proof that your closure is universal.

Where to go next