Rust - Function Pointers

Overview

Estimated time: 45–60 minutes

Master function pointers in Rust for storing and calling functions dynamically. Learn the difference between function pointers and closures, and implement higher-order programming patterns.

Learning Objectives

Prerequisites

What are Function Pointers?

Function pointers in Rust allow you to store references to functions and call them dynamically. They use the fn type and are different from closures.

Basic Function Pointer Syntax


fn add(a: i32, b: i32) -> i32 {
    a + b
}

fn multiply(a: i32, b: i32) -> i32 {
    a * b
}

fn main() {
    // Function pointer type: fn(i32, i32) -> i32
    let operation: fn(i32, i32) -> i32 = add;
    
    // Call through function pointer
    let result = operation(5, 3);
    println!("Result: {}", result);
    
    // Change the function pointer
    let operation = multiply;
    let result = operation(5, 3);
    println!("Result: {}", result);
}

Expected output:

Result: 8
Result: 15

Function Pointer Types

Different Function Signatures


fn greet() {
    println!("Hello!");
}

fn square(x: i32) -> i32 {
    x * x
}

fn process_string(s: &str) -> String {
    s.to_uppercase()
}

fn main() {
    // No parameters, no return
    let simple: fn() = greet;
    simple();
    
    // One parameter, returns value
    let math_op: fn(i32) -> i32 = square;
    println!("Square of 4: {}", math_op(4));
    
    // String processing
    let string_op: fn(&str) -> String = process_string;
    println!("Processed: {}", string_op("hello world"));
    
    // Function pointers can be stored in variables
    let operations: [fn(i32) -> i32; 1] = [square];
    println!("Using array: {}", operations[0](7));
}

Expected output:

Hello!
Square of 4: 16
Processed: HELLO WORLD
Using array: 49

Function Pointers as Parameters


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
}

// Higher-order function that takes a function pointer
fn calculate(x: i32, y: i32, operation: fn(i32, i32) -> i32) -> i32 {
    operation(x, y)
}

// Function that returns a function pointer
fn get_operation(op_type: &str) -> fn(i32, i32) -> i32 {
    match op_type {
        "add" => add,
        "subtract" => subtract,
        "multiply" => multiply,
        _ => add, // default
    }
}

fn main() {
    let x = 10;
    let y = 5;
    
    // Pass different functions to the same higher-order function
    println!("Add: {}", calculate(x, y, add));
    println!("Subtract: {}", calculate(x, y, subtract));
    println!("Multiply: {}", calculate(x, y, multiply));
    
    // Get function pointer dynamically
    let op = get_operation("multiply");
    println!("Dynamic operation: {}", op(x, y));
}

Expected output:

Add: 15
Subtract: 5
Multiply: 50
Dynamic operation: 50

Arrays of Function Pointers

Function Dispatch Tables


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 }
fn divide(a: i32, b: i32) -> i32 { a / b }

fn main() {
    // Array of function pointers
    let operations: [fn(i32, i32) -> i32; 4] = [add, subtract, multiply, divide];
    let operation_names = ["Add", "Subtract", "Multiply", "Divide"];
    
    let x = 20;
    let y = 4;
    
    // Execute all operations
    for (i, &operation) in operations.iter().enumerate() {
        let result = operation(x, y);
        println!("{}: {} = {}", operation_names[i], format!("{} {} {}", x, 
            match i { 0 => "+", 1 => "-", 2 => "*", 3 => "/", _ => "?" }, y), result);
    }
    
    // Command dispatcher pattern
    println!("\nCommand dispatcher:");
    let commands = [
        ("add", add as fn(i32, i32) -> i32),
        ("sub", subtract as fn(i32, i32) -> i32),
        ("mul", multiply as fn(i32, i32) -> i32),
        ("div", divide as fn(i32, i32) -> i32),
    ];
    
    let command = "mul";
    if let Some((_, operation)) = commands.iter().find(|(name, _)| *name == command) {
        println!("Executing {}: {}", command, operation(8, 3));
    }
}

Expected output:

Add: 20 + 4 = 24
Subtract: 20 - 4 = 16
Multiply: 20 * 4 = 80
Divide: 20 / 4 = 5

Command dispatcher:
Executing mul: 24

Function Pointers vs Closures

Key Differences


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

fn main() {
    let multiplier = 3;
    
    // Function pointer - no environment capture
    let fn_ptr: fn(i32) -> i32 = regular_function;
    
    // Closure - can capture environment
    let closure = |x: i32| x * multiplier;
    
    println!("Function pointer: {}", fn_ptr(5));
    println!("Closure: {}", closure(5));
    
    // Function pointers implement Copy
    let fn_ptr2 = fn_ptr;
    println!("Copied function pointer: {}", fn_ptr2(7));
    
    // Function pointers can be compared
    let same_fn = regular_function;
    println!("Same function: {}", fn_ptr == same_fn);
}

Expected output:

Function pointer: 10
Closure: 15
Copied function pointer: 14
Same function: true

When to Use Each


// Function pointer: No environment capture, implements Copy
fn process_with_fn_ptr(data: &[i32], processor: fn(i32) -> i32) -> Vec {
    data.iter().map(|&x| processor(x)).collect()
}

// Closure: Can capture environment, more flexible
fn process_with_closure(data: &[i32], processor: F) -> Vec 
where
    F: Fn(i32) -> i32,
{
    data.iter().map(|&x| processor(x)).collect()
}

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

fn main() {
    let numbers = [1, 2, 3, 4, 5];
    let factor = 10;
    
    // Using function pointer
    let doubled = process_with_fn_ptr(&numbers, double);
    println!("Doubled: {:?}", doubled);
    
    // Using closure with captured variable
    let scaled = process_with_closure(&numbers, |x| x * factor);
    println!("Scaled: {:?}", scaled);
    
    // Function pointers can be used where closures are expected
    let also_doubled = process_with_closure(&numbers, double);
    println!("Also doubled: {:?}", also_doubled);
}

Expected output:

Doubled: [2, 4, 6, 8, 10]
Scaled: [10, 20, 30, 40, 50]
Also doubled: [2, 4, 6, 8, 10]

Advanced Function Pointer Patterns

Callback Systems


type EventCallback = fn(&str);

struct EventSystem {
    callbacks: Vec,
}

impl EventSystem {
    fn new() -> Self {
        EventSystem {
            callbacks: Vec::new(),
        }
    }
    
    fn register_callback(&mut self, callback: EventCallback) {
        self.callbacks.push(callback);
    }
    
    fn trigger_event(&self, event: &str) {
        println!("Triggering event: {}", event);
        for callback in &self.callbacks {
            callback(event);
        }
    }
}

fn log_event(event: &str) {
    println!("  [LOG] Event occurred: {}", event);
}

fn alert_event(event: &str) {
    println!("  [ALERT] Important event: {}", event);
}

fn debug_event(event: &str) {
    println!("  [DEBUG] Event details: {}", event);
}

fn main() {
    let mut event_system = EventSystem::new();
    
    // Register callbacks
    event_system.register_callback(log_event);
    event_system.register_callback(alert_event);
    event_system.register_callback(debug_event);
    
    // Trigger events
    event_system.trigger_event("user_login");
    println!();
    event_system.trigger_event("file_uploaded");
}

Expected output:

Triggering event: user_login
  [LOG] Event occurred: user_login
  [ALERT] Important event: user_login
  [DEBUG] Event details: user_login

Triggering event: file_uploaded
  [LOG] Event occurred: file_uploaded
  [ALERT] Important event: file_uploaded
  [DEBUG] Event details: file_uploaded

Strategy Pattern with Function Pointers


type SortStrategy = fn(&mut [i32]);

fn bubble_sort(arr: &mut [i32]) {
    let len = arr.len();
    for i in 0..len {
        for j in 0..len - 1 - i {
            if arr[j] > arr[j + 1] {
                arr.swap(j, j + 1);
            }
        }
    }
}

fn selection_sort(arr: &mut [i32]) {
    let len = arr.len();
    for i in 0..len {
        let mut min_idx = i;
        for j in i + 1..len {
            if arr[j] < arr[min_idx] {
                min_idx = j;
            }
        }
        arr.swap(i, min_idx);
    }
}

fn quick_sort(arr: &mut [i32]) {
    if arr.len() <= 1 {
        return;
    }
    
    let pivot_index = partition(arr);
    let (left, right) = arr.split_at_mut(pivot_index);
    quick_sort(left);
    quick_sort(&mut right[1..]);
}

fn partition(arr: &mut [i32]) -> usize {
    let len = arr.len();
    let pivot_index = len - 1;
    let mut i = 0;
    
    for j in 0..len - 1 {
        if arr[j] <= arr[pivot_index] {
            arr.swap(i, j);
            i += 1;
        }
    }
    arr.swap(i, pivot_index);
    i
}

struct Sorter {
    strategy: SortStrategy,
}

impl Sorter {
    fn new(strategy: SortStrategy) -> Self {
        Sorter { strategy }
    }
    
    fn set_strategy(&mut self, strategy: SortStrategy) {
        self.strategy = strategy;
    }
    
    fn sort(&self, data: &mut [i32]) {
        (self.strategy)(data);
    }
}

fn main() {
    let mut data1 = [64, 34, 25, 12, 22, 11, 90];
    let mut data2 = data1.clone();
    let mut data3 = data1.clone();
    
    println!("Original: {:?}", data1);
    
    // Use different sorting strategies
    let mut sorter = Sorter::new(bubble_sort);
    sorter.sort(&mut data1);
    println!("Bubble sort: {:?}", data1);
    
    sorter.set_strategy(selection_sort);
    sorter.sort(&mut data2);
    println!("Selection sort: {:?}", data2);
    
    sorter.set_strategy(quick_sort);
    sorter.sort(&mut data3);
    println!("Quick sort: {:?}", data3);
}

Function Pointers with Generics

Generic Function Pointers


fn apply_twice(value: T, func: fn(T) -> T) -> T {
    func(func(value))
}

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

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

fn append_exclamation(s: String) -> String {
    s + "!"
}

fn main() {
    // Apply function twice on integers
    let result = apply_twice(5, increment);
    println!("Apply increment twice to 5: {}", result);
    
    let result = apply_twice(3, double);
    println!("Apply double twice to 3: {}", result);
    
    // Apply function twice on strings
    let result = apply_twice(String::from("Hello"), append_exclamation);
    println!("Apply append twice: {}", result);
}

Expected output:

Apply increment twice to 5: 7
Apply double twice to 3: 12
Apply append twice: Hello!!

Real-World Examples

Plugin System


use std::collections::HashMap;

type PluginFunction = fn(&str) -> String;

struct PluginManager {
    plugins: HashMap,
}

impl PluginManager {
    fn new() -> Self {
        PluginManager {
            plugins: HashMap::new(),
        }
    }
    
    fn register_plugin(&mut self, name: String, plugin: PluginFunction) {
        self.plugins.insert(name, plugin);
    }
    
    fn execute_plugin(&self, name: &str, input: &str) -> Option {
        self.plugins.get(name).map(|plugin| plugin(input))
    }
    
    fn list_plugins(&self) -> Vec<&String> {
        self.plugins.keys().collect()
    }
}

// Plugin implementations
fn uppercase_plugin(input: &str) -> String {
    input.to_uppercase()
}

fn reverse_plugin(input: &str) -> String {
    input.chars().rev().collect()
}

fn word_count_plugin(input: &str) -> String {
    let count = input.split_whitespace().count();
    format!("Word count: {}", count)
}

fn main() {
    let mut plugin_manager = PluginManager::new();
    
    // Register plugins
    plugin_manager.register_plugin("uppercase".to_string(), uppercase_plugin);
    plugin_manager.register_plugin("reverse".to_string(), reverse_plugin);
    plugin_manager.register_plugin("wordcount".to_string(), word_count_plugin);
    
    let input = "Hello Rust World";
    println!("Input: {}", input);
    println!("Available plugins: {:?}", plugin_manager.list_plugins());
    
    // Execute plugins
    if let Some(result) = plugin_manager.execute_plugin("uppercase", input) {
        println!("Uppercase: {}", result);
    }
    
    if let Some(result) = plugin_manager.execute_plugin("reverse", input) {
        println!("Reverse: {}", result);
    }
    
    if let Some(result) = plugin_manager.execute_plugin("wordcount", input) {
        println!("Word count: {}", result);
    }
}

Expected output:

Input: Hello Rust World
Available plugins: ["reverse", "wordcount", "uppercase"]
Uppercase: HELLO RUST WORLD
Reverse: dlroW tsuR olleH
Word count: Word count: 3

State Machine


#[derive(Debug, Clone, Copy, PartialEq)]
enum State {
    Idle,
    Running,
    Paused,
    Stopped,
}

type StateHandler = fn(&mut StateMachine, &str);

struct StateMachine {
    current_state: State,
    handlers: HashMap,
}

impl StateMachine {
    fn new() -> Self {
        let mut sm = StateMachine {
            current_state: State::Idle,
            handlers: HashMap::new(),
        };
        
        // Register state handlers
        sm.handlers.insert(State::Idle, idle_handler);
        sm.handlers.insert(State::Running, running_handler);
        sm.handlers.insert(State::Paused, paused_handler);
        sm.handlers.insert(State::Stopped, stopped_handler);
        
        sm
    }
    
    fn handle_event(&mut self, event: &str) {
        println!("Current state: {:?}, Event: {}", self.current_state, event);
        
        if let Some(&handler) = self.handlers.get(&self.current_state) {
            handler(self, event);
        }
        
        println!("New state: {:?}\n", self.current_state);
    }
    
    fn transition_to(&mut self, new_state: State) {
        self.current_state = new_state;
    }
}

fn idle_handler(sm: &mut StateMachine, event: &str) {
    match event {
        "start" => sm.transition_to(State::Running),
        _ => println!("  Cannot handle '{}' in Idle state", event),
    }
}

fn running_handler(sm: &mut StateMachine, event: &str) {
    match event {
        "pause" => sm.transition_to(State::Paused),
        "stop" => sm.transition_to(State::Stopped),
        _ => println!("  Cannot handle '{}' in Running state", event),
    }
}

fn paused_handler(sm: &mut StateMachine, event: &str) {
    match event {
        "resume" => sm.transition_to(State::Running),
        "stop" => sm.transition_to(State::Stopped),
        _ => println!("  Cannot handle '{}' in Paused state", event),
    }
}

fn stopped_handler(sm: &mut StateMachine, event: &str) {
    match event {
        "reset" => sm.transition_to(State::Idle),
        _ => println!("  Cannot handle '{}' in Stopped state", event),
    }
}

use std::collections::HashMap;

fn main() {
    let mut state_machine = StateMachine::new();
    
    // Simulate events
    state_machine.handle_event("start");
    state_machine.handle_event("pause");
    state_machine.handle_event("resume");
    state_machine.handle_event("stop");
    state_machine.handle_event("reset");
    state_machine.handle_event("invalid");
}

Expected output:

Current state: Idle, Event: start
New state: Running

Current state: Running, Event: pause
New state: Paused

Current state: Paused, Event: resume
New state: Running

Current state: Running, Event: stop
New state: Stopped

Current state: Stopped, Event: reset
New state: Idle

Current state: Idle, Event: invalid
  Cannot handle 'invalid' in Idle state
New state: Idle

Common Pitfalls

Function Pointer vs Closure Confusion


fn main() {
    let x = 10;
    
    // This won't work - closures that capture can't be converted to fn
    // let closure = |y| x + y;  // Captures x
    // let fn_ptr: fn(i32) -> i32 = closure;  // Error!
    
    // This works - no capture
    let closure = |y: i32| y * 2;  // No capture
    let fn_ptr: fn(i32) -> i32 = closure;  // OK
    
    println!("Function pointer result: {}", fn_ptr(5));
    
    // Function pointers can be used as closures
    fn multiply_by_3(x: i32) -> i32 {
        x * 3
    }
    
    let numbers = vec![1, 2, 3, 4];
    let result: Vec = numbers.iter().map(|&x| multiply_by_3(x)).collect();
    println!("Mapped: {:?}", result);
}

Best Practices

When to Use Function Pointers

Function Pointer Guidelines

Checks for Understanding

Question 1: Function Pointer Type

Q: What's the type of a function pointer that takes two &str parameters and returns a String?

Click to see answer

A: fn(&str, &str) -> String

Function pointer types include the parameter types and return type in their signature.

Question 2: Comparison

Q: Can you compare two function pointers for equality?

Click to see answer

A: Yes, function pointers can be compared for equality. Two function pointers are equal if they point to the same function.

Question 3: Environment Capture

Q: Can function pointers capture variables from their environment?

Click to see answer

A: No, function pointers cannot capture environment variables. Only closures can capture their environment. Function pointers are just addresses of functions.


← PreviousNext →