Function Pointers vs Closures
fn(i32) -> i32 is a function pointer - no captured environment. Closures may capture state and have unique types.
Recipe
fn apply(f: fn(i32) -> i32, x: i32) -> i32 { f(x) }
fn inc(x: i32) -> i32 { x + 1 }
fn main() {
println!("{}", apply(inc, 5));
}When to reach for this: C FFI callbacks, simple hooks without capture, and Send + Sync guarantees of fn pointers.
Working Example
fn call_with<F: Fn(i32) -> i32>(f: F, x: i32) -> i32 { f(x) }
fn double(x: i32) -> i32 { x * 2 }
fn main() {
println!("{}", call_with(double, 3));
println!("{}", call_with(|x| x + 1, 3));
}What this demonstrates:
- Named fn coerces to fn pointer where expected
- Generic
Fnaccepts both fn item and closure - Closures with captures cannot be
fnpointer
Deep Dive
Function items coerce to fn pointers. Closures without captures can coerce to fn pointers in some cases. FFI uses extern "C" fn.
Gotchas
- Expecting fn pointer with capturing closure - Fails coercion. Fix: Generic
F: FnorBox<dyn Fn>. - Fn pointer null - Nullable in FFI - not in safe Rust without Option.
- Method as fn pointer - Need plain fn or closure wrapping self.
- Transmute fn - UB if wrong. Fix: Correct signatures.
- Performance myth - Closures without capture often same as fn.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| Closure | Need capture | FFI needs extern fn |
extern "C" fn | C interop | Pure Rust only |
Box<dyn Fn> | Heterogeneous callbacks | fn pointer sufficient |
FAQs
size fn pointer?
One word - address.closure size?
Environment + fn ptr varies.coerce closure to fn?
Only non-capturing closures.Send Sync fn?
fn pointers Send+Sync always.variadic fn?
extern C only unsafe.method pointer?
Not same as fn - use closure or fn(item).tokio callback?
Often Arc+dyn or async block.compare?
Fn trait objects for runtime choice.no_std?
fn pointers in core.test?
Pass inc fn to apply.Related
Stack versions: This page was written for Rust 1.97.0 (edition 2024), Tokio 1.x, Axum 0.8, serde 1.0, sqlx 0.8, clap 4, and Polars 0.46+.