Closures

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