8.9 Function Pointers and Higher-Order Functions
In Rust, functions are first-class citizens. This means they can be treated like other values: assigned to variables, passed as arguments to other functions, and returned from functions.
8.9.1 Function Pointers
A variable or parameter can hold a function pointer, which references a specific function. The type of a function pointer is denoted by fn
followed by the parameter types and return type. For example, fn(i32, i32) -> i32
is the type of a pointer to a function that takes two i32
s and returns an i32
.
type Binop = fn(i32, i32) -> i32; fn add(a: i32, b: i32) -> i32 { a + b } fn subtract(a: i32, b: i32) -> i32 { a - b } fn multiply(a: i32, b: i32) -> i32 { a * b } // This function takes a function pointer as an argument. fn apply_operation(operation: Binop, x: i32, y: i32) -> i32 { operation(x, y) // Call the function via the pointer } fn main() { let operation_to_perform: Binop; // Use type alias operation_to_perform = add; // Assign 'add' function to the pointer variable println!("Result of add: {}", apply_operation(operation_to_perform, 10, 5)); operation_to_perform = subtract; // Reassign to 'subtract' function println!("Result of subtract: {}", apply_operation(operation_to_perform, 10, 5)); // You can also pass the function name directly where a pointer is expected. println!("Directly passing multiply: {}", apply_operation(multiply, 10, 5)); }
Note: When assigning functions to variables, as in let bo: Binop = add;
, the &
operator is not required on the function name.
Safety and Restrictions
- Despite the term function pointer, Rust’s function pointers are safe and type-checked. It is not possible to call invalid or uninitialized addresses, as can happen in C.
- Their capabilities are intentionally limited: they cannot be cast to arbitrary integers or used for unchecked jumps, unlike raw pointers in unsafe C code.
Function pointer types represent functions whose exact identity may not be known at compile time. Function pointers are useful for implementing callbacks, strategy patterns, or selecting behavior dynamically based on data. However, using function pointers can sometimes inhibit compiler optimizations like inlining compared to direct function calls or monomorphized generics.
8.9.2 Higher-Order Functions
A function that either takes another function as an argument or returns a function is called a higher-order function. apply_operation
in the example above is a higher-order function because it takes operation
(a function pointer) as an argument.
Functions can also return function pointers:
type Binop = fn(i32, i32) -> i32; // Using the type alias from before fn get_HOF_operation(operator: char) -> Binop { // Return type is Binop fn add(a: i32, b: i32) -> i32 { a + b } fn subtract(a: i32, b: i32) -> i32 { a - b } match operator { '+' => add, // Return a pointer to the 'add' function '-' => subtract, // Return a pointer to the 'subtract' function _ => panic!("Unknown operator"), } } fn main() { let op = get_HOF_operation('+'); println!("Result (10 + 3): {}", op(10, 3)); // Call the returned function let op2 = get_HOF_operation('-'); println!("Result (10 - 3): {}", op2(10, 3)); }
While function pointers are useful, closures (Chapter 12) are often more flexible in Rust because they can capture variables from their environment, whereas function pointers cannot. Higher-order functions frequently work with closures in idiomatic Rust code (e.g., methods like map
, filter
, fold
on iterators).