Question
Consider this Rust code:
trait Trait<T> {}
fn foo<'a>(_b: Box<dyn Trait<&'a usize>>) {}
fn bar(_b: Box<dyn for<'a> Trait<&'a usize>>) {}
Both foo and bar appear to accept a trait object implementing Trait for a reference to usize. However, foo uses one named lifetime parameter, while bar uses for<'a> syntax.
What is the difference between these two function signatures? In which situations would for<'a> higher-ranked trait bounds be necessary, especially in code involving closures?
Short Answer
You will learn that foo requires an implementation for one selected lifetime, while bar requires an implementation that works for every possible lifetime. This distinction is called a higher-ranked trait bound (HRTB) and is useful for callbacks, closures, visitors, and APIs that temporarily borrow data before passing it to user code.
Concept
A normal lifetime parameter, such as 'a in fn foo<'a>, represents one particular lifetime chosen for a use of the function.
fn foo<'a>(_b: Box<dyn Trait<&'a usize>>) {}
foo requires the boxed value to implement:
Trait<&'a usize>
for one lifetime 'a. The caller and compiler may choose which lifetime that is, but the implementation only needs to support that one chosen lifetime.
By contrast:
fn bar(_b: Box<dyn for<'a> Trait<&'a usize>>) {}
requires the boxed value to implement Trait<&'a usize> for all lifetimes 'a. Read for<'a> as:
For every possible lifetime
'a, this value implementsTrait<&'a usize>.
This is a higher-ranked trait bound because the lifetime is quantified inside the trait requirement rather than being a lifetime selected for the enclosing function call.
The distinction matters whenever your code creates temporary borrows and passes them to another value, especially a callback. A callback that must accept references to values borrowed only briefly needs to work with references of arbitrary lifetimes, not one preselected lifetime.
Mental Model
Think of a regular lifetime bound as a lock that accepts one chosen key.
Trait<&'a usize>means: “This implementation accepts references carrying the particular key'a.”for<'a> Trait<&'a usize>means: “This implementation accepts references carrying any lifetime key.”
foo needs an object that can handle one agreed-upon borrowing period. bar needs an object that can handle references no matter how short or long their borrowing period is.
This is useful when the function supplying the reference controls the borrow duration. The callback cannot predict that duration in advance, so it must be able to accept any valid one.
Syntax and Examples
The general syntax for a higher-ranked trait bound is:
for<'lifetime> SomeTrait<...>
It can appear in a trait object:
Box<dyn for<'a> Trait<&'a usize>>
or in a where clause:
fn use_callback<F>(callback: F)
where
F: for<'a> Fn(&'a str),
{
callback("hello");
}
Here is a concrete comparison:
trait Trait<T> {}
struct StaticOnly;
struct AnyReference;
impl Trait<&'static usize> for StaticOnly {}
impl<'a> Trait<&'a usize> for AnyReference {}
StaticOnly implements the trait only for &'static usize, which is a reference valid for the entire program. AnyReference implements the trait for a reference with any lifetime.
Therefore, AnyReference can satisfy both kinds of requirements, but StaticOnly cannot satisfy the for<'a> requirement:
fn foo<'a>(_value: Box<dyn Trait<&'a usize>>) {}
fn bar(_value: Box<dyn for<'a> Trait<&'a usize>>) {}
static NUMBER: usize = 42;
fn main() {
foo(Box::new(StaticOnly));
foo(Box::new(AnyReference));
bar(Box::new(AnyReference));
// Does not compile: StaticOnly supports only &'static usize,
// not every possible &'a usize.
// bar(Box::new(StaticOnly));
let _ = NUMBER;
}
In practice, the Fn, FnMut, and FnOnce traits are common places to encounter this idea.
Step by Step Execution
Consider a callback that is called with references to items owned by a slice:
fn visit_names<F>(names: &[String], mut visit: F)
where
F: for<'a> FnMut(&'a str),
{
for name in names {
visit(name.as_str());
}
}
fn main() {
let names = vec![String::from("Ada"), String::from("Grace")];
visit_names(&names, |name| {
println!("Hello, {name}!");
});
}
Execution proceeds as follows:
maincreates aVec<String>namednames.visit_namesreceives a borrow of that vector and a closure.- The loop borrows each
Stringin turn. name.as_str()produces a&strwhose lifetime is tied to the current borrow.visitis called with that&str.for<'a> FnMut(&'a str)guarantees that the closure can be called with a&strof any valid lifetime, including each temporary borrow made by the loop.
The closure does not need to know or name the lifetime. It simply promises that it can accept a borrowed string whenever visit_names provides one.
Real World Use Cases
Higher-ranked trait bounds are useful when an API lends out references temporarily.
- Collection visitors: A function iterates through a collection and calls user code with
&Tvalues. - Parser callbacks: A parser passes slices such as
&stror&[u8]into a handler without giving ownership away. - Database or cache access: An API lends a borrowed record to a callback and ensures the record cannot escape the access scope.
- Serialization and deserialization helpers: Code may need to work with borrowed input data regardless of the input borrow's lifetime.
- Middleware and request handlers: A framework may call a handler with a temporarily borrowed request or context.
- Visitor patterns: A tree walker lends each node to a visitor one at a time.
For example, a tree traversal can require a callback that works for references to any visited node:
fn visit_values<F>(values: &[i32], mut visitor: F)
where
F: for<'a> FnMut(&'a i32),
{
for value in values {
visitor(value);
}
}
Real Codebase Usage
In real Rust code, for<'a> often appears when a generic type must work with borrows created internally by a function.
A common callback pattern is:
fn with_item<T, F, R>(item: &T, operation: F) -> R
where
F: for<'a> FnOnce(&'a T) -> R,
{
operation(item)
}
The important design rule is that the callback may inspect the borrowed T, but it cannot return a reference tied to the temporary callback argument unless the API explicitly models that relationship.
Higher-ranked bounds also appear in trait bounds on generic types:
fn print_all<F>(values: &[String], mut print: F)
where
F: for<'a> FnMut(&'a String),
{
for value in values {
print(value);
}
}
In many closure-oriented APIs, Rust can infer the necessary higher-ranked behavior. You may not have to write for<'a> yourself. However, writing it explicitly is useful when:
- defining a reusable generic API,
- using a trait object such as
dyn for<'a> Fn(&'a T), - resolving a lifetime error where a callback must accept shorter-lived borrows,
- documenting that a trait implementation must work for every borrow lifetime.
A useful related pattern is a guard-style API that lends access only during a closure call:
fn with_config<F>(config: &str, use_config: F)
where
F: for<'a> FnOnce(&'a str),
{
use_config(config);
}
The callback receives access, uses it immediately, and cannot retain that borrowed reference beyond what the type system permits.
Common Mistakes
Assuming for<'a> means one long-lived reference
It means the opposite: the implementation must accept references with any valid lifetime, including very short ones.
// Means: callable with a &str for every possible 'a.
for<'a> Fn(&'a str)
Treating foo and bar as equivalent
They differ in what the implementer promises:
fn foo<'a>(_b: Box<dyn Trait<&'a usize>>) {}
// One selected lifetime.
fn bar(_b: Box<dyn for<'a> Trait<&'a usize>>) {}
// Every possible lifetime.
An implementation specialized to &'static usize can potentially meet a matching single-lifetime requirement, but it cannot meet the universal requirement in bar.
Using 'static to silence a lifetime error
This is usually too restrictive:
fn needs_static<F>(callback: F)
where
F: Fn(&'static str),
{
callback("fixed text");
}
A callback requiring &'static str cannot accept a borrowed substring from a local String. If the callback should accept temporary borrows, use a higher-ranked bound instead:
fn accepts_any_borrow<F>(callback: F)
where
F: for<'a> Fn(&'a str),
{
let text = String::from("temporary text");
callback(&text);
}
Trying to return a borrowed callback argument without expressing its lifetime
This signature promises a &str with an unconstrained output lifetime and is not a valid way to return the input borrow:
// Broken: the returned reference lifetime is not connected to the input.
// fn apply<F>(f: F) -> &str
// where
// F: for<'a> Fn(&'a str) -> &str,
// {
// f("hello")
// }
When the output must borrow from the input, connect the lifetimes explicitly in the callable bound:
fn first_char<F>(input: &str, f: F) -> &str
where
F: for<'a> Fn(&'a str) -> &'a str,
{
f(input)
}
Adding for<'a> when one named lifetime expresses the intended relationship
Use a regular named lifetime when references must be tied together, such as an input and output that borrow for the same duration:
fn identity<'a>(text: &'a str) -> &'a str {
text
}
HRTBs are for “works for every borrow lifetime,” not a replacement for all ordinary lifetime annotations.
Comparisons
| Form | Meaning | Typical use |
|---|---|---|
Trait<&'a T> | Implements the trait for one particular lifetime 'a. | Tie a trait requirement to another input or output lifetime. |
for<'a> Trait<&'a T> | Implements the trait for every possible lifetime 'a. | Callbacks, visitors, and borrowed values created inside an API. |
Fn(&str) | Often understood by Rust as accepting borrowed strings generally; explicit HRTB may be needed in more complex bounds. | Simple callback signatures. |
for<'a> Fn(&'a str) | Explicitly states that the callback accepts &str with any lifetime. | Reusable generic APIs and precise trait-object bounds. |
Fn(&'static str) | Accepts only string references valid for the entire program. | Static messages, global data, and deliberately restricted APIs. |
A regular lifetime parameter answers: “Which specific borrow is this related to?”
A higher-ranked lifetime answers: “Can this code handle whatever borrow lifetime I give it?”
Cheat Sheet
// One lifetime selected for this function call:
fn f<'a, T>(_x: T)
where
T: Trait<&'a usize>,
{
}
// Every possible lifetime:
fn g<T>(_x: T)
where
T: for<'a> Trait<&'a usize>,
{
}
// Higher-ranked trait object:
type Handler = Box<dyn for<'a> Fn(&'a str)>;
// Input and output use the same arbitrary lifetime:
fn transform<F>(input: &str, f: F) -> &str
where
F: for<'a> Fn(&'a str) -> &'a str,
{
f(input)
}
- Read
for<'a>as for every lifetime'a. - Use it when a trait implementation or callback must accept temporarily borrowed values created by your API.
- Do not replace
for<'a>with'static;'staticaccepts only extremely long-lived references. - Use a normal
'awhen you need to express a specific relationship between input and output borrows. for<'a> Fn(&'a T)is especially common for callbacks and visitor-style APIs.
FAQ
What does for<'a> mean in Rust?
It is a higher-ranked lifetime bound. It means the following trait requirement must hold for every valid lifetime 'a.
Is for<'a> Trait<&'a T> the same as Trait<&'static T>?
No. &'static T is one specific, very long-lived reference type. for<'a> Trait<&'a T> requires support for all lifetimes, including short local borrows.
Why does bar require more than foo?
foo needs an implementation for one selected lifetime. bar needs one implementation that works regardless of which lifetime is used, so its requirement is stronger.
When should I use higher-ranked trait bounds?
Use them when your function gives a callback temporary borrowed access to data and the callback must work for any valid duration of that borrow.
Why are HRTBs common with closures?
Closures are frequently called with references created inside loops, traversals, parsers, or helper functions. Those references can have different lifetimes, so the closure must accept arbitrary valid borrows.
Do I always need to write for<'a> for a closure taking &str?
No. Rust often infers the appropriate behavior for simple closure bounds. Explicit for<'a> is most useful in complex generic bounds, trait objects, and APIs where you want to state the requirement precisely.
Can a higher-ranked callback return a reference?
Yes, but the output lifetime must be connected to the input when it borrows from that input, such as for<'a> Fn(&'a str) -> &'a str.
Does for<'a> create a lifetime?
No. It does not create or extend a borrow. It states that a trait implementation works for every lifetime the surrounding code may use.
Mini Project
Description
Build a small visitor-style utility that processes borrowed strings. It demonstrates why a callback should accept a &str with any valid lifetime: the utility borrows each string from a collection and passes that temporary borrow to the callback.
Goal
Create a function that visits every name in a list and allows a closure to inspect each borrowed name.
Requirements
Requirement 1
Keep learning
Related questions
Absolute Values in Rust: Using the abs() Method
Learn how to calculate absolute values in modern Rust with abs(), handle signed integer overflow, and choose safer alternatives.
Accepting Iterators in Rust Functions with impl Iterator
Learn how to write Rust functions that accept HashMap iterators using generic bounds or impl Iterator, avoiding trait object and lifetime errors.
Access Enum Variant Values in Rust with Pattern Matching
Learn how to extract and print values stored in Rust enum variants using match and if let pattern matching, with practical examples.