Returning Closures
Because every closure expression has a unique, anonymous compiler-generated type that cannot be written directly in a function signature, functions that return a closure use either an opaque return type (-> impl Fn(...)) or a heap-allocated trait object (-> Box<dyn Fn(...)>).
Returning a Single Closure Type with impl Fn
When a function returns a single closure expression (and transfers ownership of local variables into it with move), use -> impl Fn(...) (or impl FnMut(...) / impl FnOnce(...)) for zero-cost static dispatch with no heap allocation:
fn make_linear_scaler(slope: f64, intercept: f64) -> impl Fn(f64) -> f64 {
move |x| slope * x + intercept
}
fn main() {
let celsius_to_fahrenheit = make_linear_scaler(1.8, 32.0);
println!("0 °C = {:.1} °F", celsius_to_fahrenheit(0.0));
println!("100 °C = {:.1} °F", celsius_to_fahrenheit(100.0));
}
0 °C = 32.0 °F
100 °C = 212.0 °F
Why Multiple Closure Branches Fail with impl Fn (E0308)
-> impl Fn(...) must resolve to a single concrete underlying type. Even if two closures in an if / else expression have identical parameter and return types, they have distinct anonymous compiler types and cannot both be returned via impl Fn:
// ❌ does not compile
fn make_transform(negate: bool, offset: i32) -> impl Fn(i32) -> i32 {
if negate {
move |x| -(x + offset)
} else {
move |x| x + offset
}
}
fn main() {}
error[E0308]: `if` and `else` have incompatible types
--> src/main.rs:6:9
|
3 | / if negate {
4 | | move |x| -(x + offset)
| | ---------------------- expected because of this
5 | | } else {
6 | | move |x| x + offset
| | ^^^^^^^^^^^^^^^^^^^ expected closure, found a different closure
7 | | }
| |_____- `if` and `else` have incompatible types
|
= note: expected closure `{closure@src/main.rs:4:9: 4:17}`
found closure `{closure@src/main.rs:6:9: 6:17}`
= note: no two closures, even if identical, have the same type
= help: consider boxing your closure and/or using it as a trait object
Returning Different Closures Conditionally with Box<dyn Fn>
When a function needs to return different closures across conditional branches, wrap each branch in Box::new(...) and return Box<dyn Fn(...)>:
fn make_transform(negate: bool, offset: i32) -> Box<dyn Fn(i32) -> i32> {
if negate {
Box::new(move |x| -(x + offset))
} else {
Box::new(move |x| x + offset)
}
}
fn main() {
let normal = make_transform(false, 5);
let negated = make_transform(true, 5);
println!("Normal(10): {}", normal(10));
println!("Negated(10): {}", negated(10));
}
Normal(10): 15
Negated(10): -15
impl Fn vs. Box<dyn Fn> Comparison
| Approach | Heap Allocation | Dispatch | Can Return Different Closures from Branches? |
|---|---|---|---|
-> impl Fn(A) -> R |
None (stack value) | Static (inlinable) | No (unless condition is moved inside a single closure) |
-> Box<dyn Fn(A) -> R> |
1 heap allocation (Box) |
Dynamic (vtable call) | Yes |