Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Function Pointers

Goal of This Episode

Meet the function pointer type, and learn to pass and store function names as values.

Concept

In Rust, functions can be more than called — they can be passed around like values, stored in variables, and put into Vecs. To do that, we need to meet the function pointer type.

How Function Pointers Are Written

Suppose you have a function:

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

fn main() {}

This function’s pointer type is fn(i32) -> i32. Note the lowercase fn — it denotes the function pointer type, not the keyword for defining functions.

Storing a Function in a Variable

You can assign a function’s name directly to a variable:

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

fn main() {
    let f: fn(i32) -> i32 = add_one;
}

Afterward, calling f(10) works the same as calling add_one(10) directly.

Passing Functions as Arguments

One of the most common uses of function pointers is “passing one function to another”:

fn apply(f: fn(i32) -> i32, value: i32) -> i32 {
    f(value)
}

fn main() {}

Now apply accepts any function with the signature fn(i32) -> i32 — very flexible.

Multiple Parameters and Different Return Types

A function pointer’s type is determined by its parameters and return value:

  • No parameters, no return value: fn().
  • Two parameters: fn(i32, i32) -> i32.
  • Returning a String: fn(&str) -> String.

Function Pointers vs Next Episode’s Closures

The function pointer fn(...) -> ... is a concrete type with a fixed size. But a function can’t reach the local variables inside another function — to use one, you have to pass it in as a parameter. Next episode introduces closures, which are different: wherever a closure is written, whatever local variables sit next to it are simply available, without being passed in one by one.

Example Code

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

fn double(x: i32) -> i32 {
    x * 2
}

fn apply(f: fn(i32) -> i32, value: i32) -> i32 {
    f(value)
}

fn pick_function(use_double: bool) -> fn(i32) -> i32 {
    if use_double {
        double
    } else {
        add_one
    }
}

fn main() {
    // Storing a function in a variable
    let f: fn(i32) -> i32 = add_one;
    println!("f(5) = {}", f(5));

    // Passing functions as arguments
    println!("apply(add_one, 10) = {}", apply(add_one, 10));
    println!("apply(double, 10) = {}", apply(double, 10));

    // Functions can be return values too
    let chosen = pick_function(true);
    println!("chosen(7) = {}", chosen(7));

    let chosen2 = pick_function(false);
    println!("chosen2(7) = {}", chosen2(7));

    // Putting functions in a Vec
    let operations: Vec<fn(i32) -> i32> = vec![add_one, double];
    for op in &operations {
        println!("op(3) = {}", op(3));
    }
}

Recap

  • The function pointer type is written fn(param_types) -> return_type — note the lowercase fn.
  • A function’s name works directly as a value: assign it to variables, pass it to other functions, store it in containers like Vec.
  • A function can’t reach the local variables inside another function; anything it needs must be passed in as a parameter. Next episode’s closures can use the local variables sitting next to where they’re written.