Function Pointers, Closures
Here's an example of a closure that implements FnOnce by capturing and consuming (moving) a variable from its environment:
fn main() { let name = String::from("Alice"); // A String we'll move into the closure // This closure implements FnOnce because it moves 'name' let greet = || { println!("Hello, {}!", name); name // This moves 'name' out of the closure }; // We can only call this once because it consumes 'name' let moved_name = greet(); println!("Moved name: {}", moved_name); // greet(); // This would fail - can't call FnOnce twice! // Demonstrate passing to a function that expects FnOnce run_once(greet); // Note: we can't actually do this because greet was already consumed } fn run_once<F: FnOnce() -> String>(f: F) { let result = f(); println!("From run_once: {}", result); }
Key points about FnOnce:
- Captures and consumes environment variables (moves them)
- Can only be called once because it consumes what it captures
- Most restrictive closure trait (all closures implement
FnOnce)
A working version that demonstrates passing to run_once:
fn main() { let name = String::from("Bob"); // This closure moves 'name' let greet = move || { println!("Hello, {}!", name); name }; // We can pass it to a function expecting FnOnce run_once(greet); // Can't use 'greet' or 'name' after this point } fn run_once<F: FnOnce() -> String>(f: F) { let result = f(); println!("From run_once: {}", result); }
Why this is FnOnce:
- The closure uses
moveto take ownership ofname - It returns
name, moving it out of the closure - After the first call, the closure can't be called again because its captured value was moved
Relationship between the traits:
- All closures implement
FnOnce - Closures that don't move captured values implement
FnMut - Closures that don't mutate captured values implement
Fn
So: Fn is a subset of FnMut which is a subset of FnOnce
In Rust, closure parameters don't require explicit type annotations in most cases because the compiler can infer their types from the context. This is different from regular fn functions which always require type annotations.
Closure Type Inference Example:
fn main() { // Type inferred from usage let add = |a, b| a + b; println!("{}", add(3, 5)); // Compiler knows a and b are i32 // Type inferred from variable annotation let add: fn(i32, i32) -> i32 = |a, b| a + b; }
When Types Are Needed:
- Ambiguous Cases (when the compiler can't infer):
#![allow(unused)] fn main() { let ambiguous = |x| x; // Error: cannot infer type ambiguous(5); // Adding this would fix it }
- Explicitly Wanted (for clarity):
#![allow(unused)] fn main() { let add = |a: i32, b: i32| -> i32 { a + b }; }
Your Specific Example:
#![allow(unused)] fn main() { let mut increment_and_add = |a, b| { // a and b types inferred from first usage // If you later call: increment_and_add(1u8, 2u8), // then a and b would be u8 }; }
Key Differences from Regular Functions:
| Feature | Closure (||) | Regular Function (fn) |
|---------------|---------------|----------------------|
| Type annotations | Optional | Required |
| Capturing env | Yes | No |
| Traits | Fn, FnMut, FnOnce | N/A |
| As parameters | Usually generic with trait bounds | Directly by type |
Best Practice:
- Omit types when they're obvious from context
- Add types when:
- The closure is complex
- You want to document expected types
- The compiler can't infer them