Advanced Level
Advanced Functions and Closures
Chapter 21: Advanced Functions and Closures š§
Master function pointers, closures, and advanced function features in Rust.
Function Pointers
Basic Function Pointers
Function pointers allow you to store and pass functions as values:
fn add_one(x: i32) -> i32 {
x + 1
}
fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 {
f(f(arg))
}
fn main() {
let answer = do_twice(add_one, 5);
println!("The answer is: {}", answer);
}Function Pointers vs Closures
fn main() {
// Function pointer
let fp: fn(i32) -> i32 = add_one;
// Closure
let closure = |x: i32| x + 1;
println!("Function pointer result: {}", fp(5));
println!("Closure result: {}", closure(5));
}Advanced Closures
Closure Traits
Closures automatically implement one of these traits:
Fn- borrows variables from environment immutablyFnMut- borrows variables from environment mutablyFnOnce- takes ownership of variables from environment
fn main() {
let x = 5;
// Implements Fn because it only reads x
let equal_to_x = |z| z == x;
println!("{}", equal_to_x(5));
let mut y = 5;
// Implements FnMut because it mutates y
let mut add_to_y = |z| {
y += z;
y
};
println!("{}", add_to_y(3));
let w = vec![1, 2, 3];
// Implements FnOnce because it takes ownership of w
let consume_w = move || {
println!("{:?}", w);
w.len()
};
println!("Length: {}", consume_w());
// println!("{:?}", w); // Error: w was moved
}Using Closures with Iterator Methods
fn main() {
let numbers = vec![1, 2, 3, 4, 5];
// Closure that implements Fn
let squared: Vec<i32> = numbers.iter().map(|x| x * x).collect();
// Closure that implements FnMut
let mut counter = 0;
let counted: Vec<i32> = numbers.iter().map(|x| {
counter += 1;
x * counter
}).collect();
println!("{:?}", squared);
println!("{:?}", counted);
}Returning Closures
Using Box for Trait Objects
fn returns_closure() -> Box<dyn Fn(i32) -> i32> {
Box::new(|x| x + 1)
}
fn main() {
let closure = returns_closure();
println!("{}", closure(5));
}Using impl Trait
fn returns_closure_impl() -> impl Fn(i32) -> i32 {
|x| x + 1
}
fn main() {
let closure = returns_closure_impl();
println!("{}", closure(5));
}Function Parameters
Accepting Functions and Closures
fn apply_function<F>(f: F, x: i32) -> i32
where
F: Fn(i32) -> i32,
{
f(x)
}
fn add_five(x: i32) -> i32 {
x + 5
}
fn main() {
// Using function pointer
let result1 = apply_function(add_five, 10);
// Using closure
let result2 = apply_function(|x| x * 2, 10);
println!("Result 1: {}", result1);
println!("Result 2: {}", result2);
}Higher-Ranked Trait Bounds
for<'a> Syntax
fn call_with_ref<F>(f: F) -> i32
where
F: for<'a> Fn(&'a i32) -> i32,
{
let x = 42;
f(&x)
}
fn main() {
let result = call_with_ref(|y| *y + 1);
println!("Result: {}", result);
}Practical Examples
Example 1: Event Handler System
struct EventHandler<T> {
handlers: Vec<Box<dyn Fn(T) -> bool>>,
}
impl<T> EventHandler<T> {
fn new() -> EventHandler<T> {
EventHandler {
handlers: Vec::new(),
}
}
fn add_handler<F>(&mut self, handler: F)
where
F: Fn(T) -> bool + 'static,
{
self.handlers.push(Box::new(handler));
}
fn handle_event(&self, event: T) -> bool {
for handler in &self.handlers {
if handler(event.clone()) {
return true;
}
}
false
}
}
#[derive(Clone)]
enum Event {
Click { x: i32, y: i32 },
KeyPress(char),
Resize { width: u32, height: u32 },
}
fn main() {
let mut click_handler = EventHandler::new();
// Add handlers with closures
click_handler.add_handler(|event: Event| {
match event {
Event::Click { x, y } => {
println!("Click at ({}, {})", x, y);
true
}
_ => false,
}
});
click_handler.add_handler(|event: Event| {
match event {
Event::KeyPress(c) => {
println!("Key pressed: {}", c);
c == 'q'
}
_ => false,
}
});
// Handle events
click_handler.handle_event(Event::Click { x: 100, y: 200 });
click_handler.handle_event(Event::KeyPress('a'));
click_handler.handle_event(Event::KeyPress('q'));
}Example 2: Mathematical Operation Factory
#[derive(Debug, Clone, Copy)]
enum Operation {
Add,
Subtract,
Multiply,
Divide,
}
// Function that returns a closure
fn create_operation(op: Operation) -> Box<dyn Fn(f64, f64) -> Option<f64>> {
match op {
Operation::Add => Box::new(|a, b| Some(a + b)),
Operation::Subtract => Box::new(|a, b| Some(a - b)),
Operation::Multiply => Box::new(|a, b| Some(a * b)),
Operation::Divide => Box::new(|a, b| {
if b != 0.0 {
Some(a / b)
} else {
None
}
}),
}
}
// Function that accepts a function pointer
fn apply_operation(op_func: fn(f64, f64) -> f64, a: f64, b: f64) -> f64 {
op_func(a, b)
}
// Regular functions for each operation
fn add(a: f64, b: f64) -> f64 { a + b }
fn subtract(a: f64, b: f64) -> f64 { a - b }
fn multiply(a: f64, b: f64) -> f64 { a * b }
fn divide(a: f64, b: f64) -> f64 { a / b }
fn main() {
// Using closures
let adder = create_operation(Operation::Add);
let divider = create_operation(Operation::Divide);
println!("5 + 3 = {:?}", adder(5.0, 3.0));
println!("10 / 2 = {:?}", divider(10.0, 2.0));
println!("10 / 0 = {:?}", divider(10.0, 0.0));
// Using function pointers
println!("Function pointer results:");
println!("5 + 3 = {}", apply_operation(add, 5.0, 3.0));
println!("5 - 3 = {}", apply_operation(subtract, 5.0, 3.0));
println!("5 * 3 = {}", apply_operation(multiply, 5.0, 3.0));
println!("10 / 2 = {}", apply_operation(divide, 10.0, 2.0));
}Common Mistakes
ā Forgetting Closure Trait Bounds
fn apply_function<F>(f: F, x: i32) -> i32 {
f(x) // Error: F doesn't have a known size at compile time
}
fn main() {
let result = apply_function(|x| x * 2, 5);
println!("{}", result);
}ā Proper Closure Trait Bounds
fn apply_function<F>(f: F, x: i32) -> i32
where
F: Fn(i32) -> i32,
{
f(x)
}
fn main() {
let result = apply_function(|x| x * 2, 5);
println!("{}", result);
}ā Moving Values When Not Intended
fn main() {
let nums = vec![1, 2, 3];
let closure = || {
println!("{:?}", nums); // Error: nums was moved into closure
};
// This won't compile because nums was moved
// println!("{:?}", nums);
closure();
}ā Properly Borrowing Values
fn main() {
let nums = vec![1, 2, 3];
let closure = move || {
println!("{:?}", nums); // Now nums is moved into closure
};
closure();
// Still won't compile because nums was moved
// println!("{:?}", nums);
}Or:
fn main() {
let nums = vec![1, 2, 3];
let closure = |n: &Vec<i32>| {
println!("{:?}", n);
};
closure(&nums);
println!("{:?}", nums); // This works because we borrowed nums
}Key Takeaways
- ā Function pointers allow storing and passing functions as values
- ā
Closures automatically implement
Fn,FnMut, orFnOncetraits - ā
Use
movekeyword to transfer ownership into closures - ā Closures can capture variables from their environment
- ā Function pointers implement all three closure traits
- ā
Use
Box<dyn Fn()>orimpl Fn()to return closures from functions - ā Higher-ranked trait bounds allow for more flexible function signatures
- ā Follow Rust naming conventions and idioms for advanced functions
Ready for Chapter 22? ā Macros