Closures and Function Pointers, Revisited

Module 2 introduced closures as anonymous functions that capture their environment. But once you start passing functions around as values — storing callbacks, building higher-order APIs, returning behavior from functions — a few subtleties surface: functions and closures aren't quite the same type, returning a closure requires a boxing trick, and the `Fn` trait family has a precise structure worth knowing. These details show up constantly in real Rust APIs, and mastering them makes you fluent in Rust's functional side.

Revisiting closures (from Module 2) with an advanced lens, this post covers function pointers (fn), the relationship between functions and the closure traits (Fn/FnMut/FnOnce), returning closures (the boxing requirement), and passing functions as arguments. These come up whenever you build APIs that take or return behavior (callbacks, higher-order functions). It deepens Module 2’s closures into the practical fluency needed for real functional-style Rust.

Function pointers

Beyond closures, Rust has function pointers (fn) — the type of a plain function used as a value, which can be passed like a closure:

fn add_one(x: i32) -> i32 { x + 1 }

// `f: fn(i32) -> i32` is a function pointer parameter.
fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 {
    f(arg) + f(arg)
}

fn main() {
    let answer = do_twice(add_one, 5); // pass the function by name
    assert_eq!(answer, 12);
}

Function pointers (fn(...) -> ..., the type of a plain function used as a value) can be passed like closures (functions and closures are often interchangeable as arguments), and a function pointer implements all three closure traits (Fn/FnMut/FnOnce). Understanding the closure-trait structure clarifies this.

The closure trait family

The closure traitsFn, FnMut, FnOnce — form a hierarchy based on how a closure uses its captured environment (from Module 2, now with the structure clear):

The closure traits — Fn (read/no capture), FnMut (mutate), FnOnce (consume) — form a hierarchy (Fn ⊂ FnMut ⊂ FnOnce) based on how a closure uses its captures, and you bound closure parameters by the least restrictive trait that works (to accept the widest range). This structure types “a function passed as an argument.” Returning a closure has a special requirement.

Returning closures

Returning a closure from a function requires special handling, because closures don’t have a fixed known size — you must return them boxed (or via impl Trait):

// Return a boxed closure (a trait object) — the closure's concrete type is
// unnameable and unsized, so box it behind the Fn trait.
fn returns_closure() -> Box<dyn Fn(i32) -> i32> {
    Box::new(|x| x + 1)
}

// Or, more simply, with `impl Trait` (return "some type implementing Fn"):
fn make_adder(n: i32) -> impl Fn(i32) -> i32 {
    move |x| x + n // `move` captures n by value
}

fn main() {
    let add_one = returns_closure();
    let add_five = make_adder(5);
    assert_eq!(add_one(1), 2);
    assert_eq!(add_five(10), 15);
}

Returning a closure requires handling its unnameable, unsized type — via Box<dyn Fn...> (a boxed trait object, for returning different closure types) or impl Trait (-> impl Fn..., simpler and zero-cost for a single concrete type). This is a common real-Rust need. These features enable functional-style APIs.

Functional-style APIs in practice

Bringing it together, these features enable functional-style Rust APIs — passing and returning behavior — which are pervasive:

Closures and function pointers, revisited — function pointers (fn) as function-values, the closure-trait hierarchy (Fn/FnMut/FnOnce, bound by the loosest that works), and returning closures (via impl Trait or Box<dyn Fn>) — enable Rust’s pervasive, zero-cost functional-style APIs (passing and returning behavior). This deepens Module 2’s closures into practical fluency. Next: building a real command-line application.

Key takeaways

Further reading

Sources & References

Function pointers and returning closures