Closures with Generic Bounds
In generic structs, trait implementations, and higher-order functions, closures can be combined with additional trait bounds—such as lifetime bounds (+ 'static), thread-safety bounds (+ Send + Sync), cloning bounds (+ Clone), and Higher-Ranked Trait Bounds (for<'a> Fn(&'a T)) for closures that accept references of any lifetime.
Storing a Closure Inside a Struct
To store a closure inside a struct without dynamic dispatch (Box<dyn Fn>), parameterize the struct over a type parameter F and add the Fn* bound on the impl block:
struct RetryRunner<F> {
max_attempts: u32,
operation: F,
}
impl<F, T, E> RetryRunner<F>
where
F: FnMut(u32) -> Result<T, E>,
{
fn new(max_attempts: u32, operation: F) -> Self {
Self {
max_attempts,
operation,
}
}
fn execute(&mut self) -> Result<T, E> {
let mut attempt = 1;
loop {
match (self.operation)(attempt) {
Ok(val) => return Ok(val),
Err(err) if attempt >= self.max_attempts => return Err(err),
Err(_) => attempt += 1,
}
}
}
}
fn main() {
let mut runner = RetryRunner::new(3, |attempt| {
if attempt < 3 {
println!("Attempt {attempt} failed, retrying...");
Err("transient failure")
} else {
Ok(format!("Succeeded on attempt {attempt}"))
}
});
println!("Result: {:?}", runner.execute());
}
Attempt 1 failed, retrying...
Attempt 2 failed, retrying...
Result: Ok("Succeeded on attempt 3")
Notice the parentheses around (self.operation)(attempt): without parentheses, self.operation(attempt) looks for a method named operation on RetryRunner rather than invoking the closure stored in the field self.operation.
Combining Closure Bounds (Fn + Send + Sync + 'static)
Concurrent registries, async task spawners, and web router handlers combine Fn with Send + Sync + 'static so handlers can be stored in shared state and invoked simultaneously across worker threads:
use std::sync::Arc;
type SharedHandler = Arc<dyn Fn(&str) -> String + Send + Sync + 'static>;
fn make_handler(prefix: String) -> SharedHandler {
Arc::new(move |req: &str| format!("{prefix}: {req}"))
}
fn main() {
let handler = make_handler(String::from("200 OK"));
println!("{}", handler("GET /health"));
}
200 OK: GET /health
Higher-Ranked Trait Bounds (for<'a> Fn(&'a str) -> &'a str)
When a closure parameter is a reference whose lifetime comes from inside the callee (for example, a temporary buffer created inside a loop) rather than a lifetime fixed before the function call, the bound must work for all possible lifetimes 'a. Rust expresses this with a Higher-Ranked Trait Bound (for<'a>), which is also the implicit desugaring whenever you write a reference in an Fn bound without an explicit lifetime:
// Writing `F: Fn(&str) -> &str` automatically desugars to:
fn apply_to_local_buffer<F>(transform: F) -> String
where
F: for<'a> Fn(&'a str) -> &'a str,
{
let local_string = String::from(" higher-ranked bound ");
// `local_string` only lives inside this function, so `'a` is local:
let slice: &str = transform(&local_string);
slice.to_uppercase()
}
fn main() {
// Annotating the closure parameter type `&str` helps the compiler infer `for<'a>`:
let result = apply_to_local_buffer(|s: &str| s.trim());
println!("Result: '{result}'");
}
Result: 'HIGHER-RANKED BOUND'