Function Pointers
A function pointer (fn(T1, T2) -> R, written with a lowercase fn) is a primitive value that stores the memory address of executable machine code. Unlike closures—which may capture variables from their surrounding scope—a function pointer is a plain, stateless code address (usize / 8 bytes on 64-bit systems) that implements Copy and can be stored in variables, structs, arrays, or passed as an argument to higher-order functions.
Function Pointer Type Syntax (fn(...) -> ...)
The type of a function pointer mirrors a function declaration with the function name and parameter names removed:
| Function Definition | Matching Function Pointer Type |
|---|---|
fn add(a: i32, b: i32) -> i32 |
fn(i32, i32) -> i32 |
fn is_even(n: u64) -> bool |
fn(u64) -> bool |
fn log_message(msg: &str) |
fn(&str) (shorthand for fn(&str) -> ()) |
fn get_default() -> u32 |
fn() -> u32 |
fn add(a: i32, b: i32) -> i32 {
a + b
}
fn multiply(a: i32, b: i32) -> i32 {
a * b
}
fn apply_binary(op: fn(i32, i32) -> i32, x: i32, y: i32) -> i32 {
op(x, y)
}
fn main() {
// Store a function pointer in a variable and reassign it:
let mut operation: fn(i32, i32) -> i32 = add;
println!("add via pointer: {}", operation(6, 7));
operation = multiply;
println!("multiply via pointer: {}", operation(6, 7));
// Pass functions directly as arguments:
println!("apply_binary(add): {}", apply_binary(add, 10, 5));
}
add via pointer: 13
multiply via pointer: 42
apply_binary(add): 15
Function Items vs Function Pointers
In Rust, there is a subtle but important distinction between a function item type (the unique, zero-sized type of a specific function definition like add) and a function pointer type (fn(i32, i32) -> i32):
Function Item (`add`): Function Pointer (`fn(i32, i32) -> i32`):
- Unique compiler-internal type - Concrete 8-byte code address (`0x100003f20`)
- Size = 0 bytes (Zero-Sized Type!) - Size = 8 bytes (`size_of::<usize>()`)
- Direct static call (easily inlined) - Indirect call through pointer register
- Automatically coerces to `fn(...)`! - Can hold `add` OR `multiply` at runtime
use std::mem::size_of_val;
fn square(x: i32) -> i32 {
x * x
}
fn main() {
let fn_item = square; // Type: unique zero-sized function item `fn(i32) -> i32 {square}`
let fn_ptr: fn(i32) -> i32 = square; // Coerced to function pointer `fn(i32) -> i32`
println!("size of fn_item: {} bytes", size_of_val(&fn_item));
println!("size of fn_ptr: {} bytes (address = {:p})", size_of_val(&fn_ptr), fn_ptr as *const ());
}
size of fn_item: 0 bytes
size of fn_ptr: 8 bytes (address = 0x102a43f10)
Because add and multiply have two different zero-sized function item types, putting them into an array [add, multiply] requires coercing at least one element (or annotating the array type) as fn(i32, i32) -> i32:
fn add(a: i32, b: i32) -> i32 { a + b }
fn sub(a: i32, b: i32) -> i32 { a - b }
fn mul(a: i32, b: i32) -> i32 { a * b }
fn main() {
// Annotate the array element type as `fn(i32, i32) -> i32` so all three coerce to function pointers:
let pipeline: [fn(i32, i32) -> i32; 3] = [add, sub, mul];
for op in pipeline {
println!("op(10, 4) = {}", op(10, 4));
}
}
op(10, 4) = 14
op(10, 4) = 6
op(10, 4) = 40
Non-Capturing Closures Coerce to fn Pointers
A closure (|a, b| a + b) that does not capture any variables from its surrounding environment can also coerce automatically into a fn pointer:
fn run_transform(value: i32, f: fn(i32) -> i32) -> i32 {
f(value)
}
fn main() {
// Non-capturing closure coerced directly to `fn(i32) -> i32`:
let result = run_transform(10, |x| x * 3 + 1);
println!("Result: {result}");
}
Result: 31
If a closure captures a local variable from its environment, it cannot coerce to a fn pointer (E0308: closures can only be coerced to 'fn' types if they do not capture any variables).
Tuple Struct and Enum Variant Constructors as Function Pointers
In Rust, the name of a tuple struct (struct Meters(u32);) or a tuple-style enum variant (Some, Ok, Err) is actually a function that constructs that value! Consequently, you can pass Some or a tuple struct name anywhere a function pointer is expected:
#[derive(Debug)]
struct UserId(u64);
fn main() {
let raw_ids = [101_u64, 102, 103];
// Pass the tuple struct constructor `UserId` and enum variant `Some` as functions:
let wrapped_ids: [UserId; 3] = raw_ids.map(UserId);
let optional_ids: [Option<u64>; 3] = raw_ids.map(Some);
println!("wrapped_ids: {wrapped_ids:?}");
println!("optional_ids: {optional_ids:?}");
}
wrapped_ids: [UserId(101), UserId(102), UserId(103)]
optional_ids: [Some(101), Some(102), Some(103)]
Function Pointers Implement Fn, FnMut, and FnOnce
Every function pointer type fn(T) -> R automatically implements all three closure traits (Fn, FnMut, and FnOnce). That means if you write a generic function accepting F: Fn(i32) -> i32, callers can pass either a capturing closure, a non-capturing closure, a function item, or a fn(i32) -> i32 function pointer!