Functions

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!