Closures - Anonymous Functions
Closures are anonymous functions that can capture variables from their surrounding environment. They're incredibly useful for functional programming patterns, working with iterators, and writing concise, expressive code. Rust's closures are zero-cost abstractions with powerful type inference.
What Are Closures?
A closure is an anonymous function you can save in a variable or pass as an argument to other functions. Unlike regular functions, closures can capture values from the scope in which they're defined.
fn keyword and can't
capture environment. Closures use || syntax and can capture
variables from their surrounding scope.
Closure Syntax
Click Run to execute your code
Syntax Variations
| Syntax | Example | Use Case |
|---|---|---|
| Simple | |x| x + 1 |
Single expression, type inference |
| With types | |x: i32| -> i32 { x + 1 } |
Explicit types |
| Multiple params | |x, y| x + y |
Multiple parameters |
| Block body | |x| { let y = x * 2; y + 1 } |
Multiple statements |
Capturing the Environment
Closures can capture variables from their surrounding scope in three ways:
Click Run to execute your code
The Three Fn Traits
| Trait | Capturing | Can Call | Example |
|---|---|---|---|
Fn |
Borrows immutably | Multiple times | || println!("{}", x) |
FnMut |
Borrows mutably | Multiple times | || list.push(4) |
FnOnce |
Takes ownership | Once | || drop(x) |
Fn. If
it modifies them, FnMut. If it consumes them, FnOnce.
The move Keyword
Use move to force a closure to take ownership of captured variables:
let x = vec![1, 2, 3];
let closure = move || println!("{:?}", x);
closure();
// println!("{:?}", x); // Error: x was moved
Closures with Iterators
Click Run to execute your code
Common Iterator Methods
| Method | Purpose | Example |
|---|---|---|
map |
Transform each element | .map(|x| x * 2) |
filter |
Keep matching elements | .filter(|x| *x > 5) |
fold |
Reduce to single value | .fold(0, |acc, x| acc + x) |
find |
Find first match | .find(|x| *x == 5) |
any |
Check if any match | .any(|x| *x > 10) |
all |
Check if all match | .all(|x| *x > 0) |
Returning Closures
You can return closures using impl Fn:
fn make_adder(x: i32) -> impl Fn(i32) -> i32 {
move |y| x + y
}
let add_5 = make_adder(5);
println!("{}", add_5(10)); // 15
Type Inference
Closures have powerful type inference:
// Compiler infers types from usage
let add = |x, y| x + y;
let result = add(5, 10); // Infers i32
// Once used, types are fixed
// let result2 = add(5.0, 10.0); // Error: expected i32
Best Practices
- Use closures for short functions: Especially with iterators
- Let type inference work: Only add types when needed
- Use move for threads: Ensure closure owns its data
- Chain iterator methods: Readable, efficient functional code
- Prefer closures over loops: More expressive with iterators
- Know your Fn traits: Understand capturing behavior
Common Mistakes
1. Forgetting to dereference in closures
// Wrong - comparing references
let numbers = vec![1, 2, 3];
let evens: Vec<&i32> = numbers.iter()
.filter(|x| x % 2 == 0) // Error: can't mod &i32
.collect();
// Correct - dereference with *
let evens: Vec<&i32> = numbers.iter()
.filter(|x| *x % 2 == 0)
.collect();
2. Using closure after move
// Wrong - using moved value
let x = vec![1, 2, 3];
let closure = move || println!("{:?}", x);
closure();
println!("{:?}", x); // Error: x was moved
// Correct - clone if you need both
let x = vec![1, 2, 3];
let x_clone = x.clone();
let closure = move || println!("{:?}", x_clone);
closure();
println!("{:?}", x); // OK!
3. Trying to return closure without impl Fn
// Wrong - can't return closure type directly
fn make_adder(x: i32) -> ??? { // What type?
|y| x + y
}
// Correct - use impl Fn
fn make_adder(x: i32) -> impl Fn(i32) -> i32 {
move |y| x + y
}
Exercise: Closures Practice
Task: Practice closures with temperature conversion, filtering, and functional patterns.
Click Run to execute your code
Show Solution
let square = |x| x * x;
let base = 10;
let add_base = |x| x + base;
let fahrenheit: Vec = celsius.iter()
.map(|c| c * 9 / 5 + 32)
.collect();
let result: Vec = numbers.iter()
.filter(|x| *x > 5)
.map(|x| x * x)
.collect();
let sum: i32 = nums.iter()
.filter(|x| *x % 2 == 0)
.sum();
fn make_multiplier(factor: i32) -> impl Fn(i32) -> i32 {
move |x| x * factor
}
let max = values.iter()
.fold(0, |acc, x| if x > &acc { *x } else { acc });
Summary
- Closures are anonymous functions that capture environment
- Syntax:
|params| expressionor|params| { body } - Three Fn traits: Fn, FnMut, FnOnce
- Fn borrows immutably, can call multiple times
- FnMut borrows mutably, can call multiple times
- FnOnce takes ownership, can call once
- move keyword forces ownership transfer
- Work seamlessly with iterators
- Type inference makes closures concise
- Zero-cost abstraction - no runtime overhead
What's Next?
Now that you understand closures, you're ready to explore Iterators in depth - how to create custom iterators, use iterator adapters, and write efficient, functional code. Closures and iterators work together to enable powerful functional programming patterns in Rust.
Enjoying these tutorials?