How do you specify a closure as a function parameter or return type?
m
mayoTable of contents
Closures in Rust are anonymous types, so you must use trait bounds (Fn, FnMut, FnOnce) to define their signatures. Here’s how to work with them as parameters and return types.
Closure as a Function Parameter
Use generic type parameters with trait bounds to accept closures.
Example: Fn (Immutable Borrow)
// Accepts a closure that takes `i32` and returns `i32` (read-only).
fn apply<F: Fn(i32) -> i32>(f: F, x: i32) -> i32 {
f(x)
}
fn main() {
let add_five = |x| x + 5; // Implements `Fn`
println!("{}", apply(add_five, 10)); // 15
}
Example: FnMut (Mutable Borrow)
// Accepts a closure that mutates its environment.
fn apply_mut<F: FnMut(i32) -> i32>(mut f: F, x: i32) -> i32 {
f(x)
}
fn main() {
let mut count = 0;
let mut increment_and_add = |x| {
count += 1; // Mutates `count` → `FnMut`
x + count
};
println!("{}", apply_mut(increment_and_add, 10)); // 11
}
Closure as a Return Type
Use impl Trait for static dispatch (zero-cost) or Box<dyn Trait> for dynamic dispatch (flexible).
Example: Return impl Fn (Static Dispatch)
// Returns a closure that adds a fixed value (immutable capture).
fn make_adder(a: i32) -> impl Fn(i32) -> i32 {
move |b| a + b // `move` forces ownership (still `Fn` since `a` is read-only)
}
fn main() {
let add_ten = make_adder(10);
println!("{}", add_ten(5)); // 15
}
Example: Return Box<dyn Fn> (Dynamic Dispatch)
// Returns a trait object for heterogeneous closures.
fn create_closure(is_add: bool) -> Box<dyn Fn(i32) -> i32> {
if is_add {
Box::new(|x| x + 1) // Heap-allocated closure
} else {
Box::new(|x| x - 1)
}
}
fn main() {
let add = create_closure(true);
let sub = create_closure(false);
println!("{} {}", add(5), sub(5)); // 6 4
}
Key Differences
| Approach | impl Fn (Static) |
Box<dyn Fn> (Dynamic) |
|---|---|---|
| Dispatch | Monomorphized (zero-cost) | Vtable lookup (runtime cost) |
| Use Case | Single closure type | Multiple closure types |
| Memory | Stack-allocated | Heap-allocated (trait object) |
| Flexibility | Less (fixed type) | More (any dyn Fn closure) |
When to Use Each
impl Fn:- When returning a single type of closure (e.g., from a factory function).
- For performance-critical code (no heap allocation).
Box<dyn Fn>:- When returning different closure types (e.g., conditionally).
- For dynamic behavior (e.g., plugin systems, callbacks).
Pitfalls
FnMutin Structs: Store mutable closures withFnMutand annotatemut:struct Processor<F: FnMut(i32) -> i32> { op: F, }Lifetimes: Closures capturing references may require explicit lifetimes:
fn capture_ref<'a>(s: &'a str) -> impl Fn() -> &'a str { move || s // Closure captures `s` with lifetime `'a` }
Key Takeaways
✅ Parameter: Use generics (F: Fn(...)) for flexibility and performance.
✅ Return Type:
impl Fnfor static dispatch (fast, fixed type).Box<dyn Fn>for dynamic dispatch (flexible, multiple types).
🚀 Preferimpl Fnunless you need runtime polymorphism.
Try This: What happens if you return a FnOnce closure?
Answer: It’s allowed, but the caller can only invoke it once!
Back to blog
Share::