Closures
Closures are anonymous functions that capture variables from their enclosing scope. They're used extensively with iterators, callbacks, and anywhere you need a short inline function.
fn main() {
// Closure syntax variations
let add_one = |x: i32| -> i32 { x + 1 }; // Full annotation
let add_two = |x| x + 2; // Type inferred
let double = |x| x * 2; // Minimal
println!("{} {} {}", add_one(5), add_two(5), double(5));
// Multi-line closure
let calculate = |a: i32, b: i32| {
let sum = a + b;
let product = a * b;
(sum, product)
};
let (s, p) = calculate(3, 4);
println!("Sum: {s}, Product: {p}");
// Closures capture from the environment
let name = String::from("Rust");
let greeting = || println!("Hello, {name}!");
greeting();
greeting(); // Can call multiple times (immutable borrow)
println!("Name still valid: {name}");
// Mutable capture
let mut count = 0;
let mut increment = || {
count += 1;
count
};
println!("{}", increment()); // 1
println!("{}", increment()); // 2
// Move capture — takes ownership
let data = vec![1, 2, 3];
let owns_data = move || {
println!("I own: {:?}", data);
};
owns_data();
// println!("{:?}", data); // ERROR: data was moved
}
Fn, FnMut, FnOnce Traits
Closure Traits Hierarchy
- FnOnce: Can be called once. Consumes captured variables. All closures implement this.
- FnMut: Can be called multiple times. May mutate captured variables. Also implements FnOnce.
- Fn: Can be called multiple times. Only reads captured variables. Also implements FnMut and FnOnce.
// Accepting closures as function parameters
fn apply_twice i32>(f: F, x: i32) -> i32 {
f(f(x))
}
fn call_with_value(f: F) {
f(String::from("hello")); // Can only call once
}
fn mutate_and_call(mut f: F, times: usize) {
for _ in 0..times {
f();
}
}
// Returning closures
fn make_adder(n: i32) -> impl Fn(i32) -> i32 {
move |x| x + n
}
fn main() {
let result = apply_twice(|x| x + 3, 7);
println!("Applied twice: {result}"); // 13
call_with_value(|s| println!("Got: {s}"));
let mut total = 0;
mutate_and_call(|| total += 1, 5);
println!("Total: {total}"); // 5
let add_5 = make_adder(5);
println!("10 + 5 = {}", add_5(10));
}
Iterators
fn main() {
let v = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// Iterator adaptors (lazy — nothing happens until consumed)
let evens: Vec = v.iter()
.filter(|&&x| x % 2 == 0)
.cloned()
.collect();
println!("Evens: {:?}", evens);
let doubled: Vec = v.iter()
.map(|&x| x * 2)
.collect();
println!("Doubled: {:?}", doubled);
// Chaining adaptors
let result: Vec = v.iter()
.filter(|&&x| x > 3)
.map(|&x| x * x)
.take(3)
.collect();
println!("Filtered, squared, taken 3: {:?}", result);
// fold (like reduce)
let sum: i32 = v.iter().sum();
let product: i32 = v.iter().fold(1, |acc, &x| acc * x);
println!("Sum: {sum}, Product: {product}");
// More useful adaptors
let zipped: Vec<(i32, &str)> = vec![1, 2, 3]
.into_iter()
.zip(vec!["a", "b", "c"])
.collect();
println!("Zipped: {:?}", zipped);
let chained: Vec = vec![1, 2, 3]
.into_iter()
.chain(vec![4, 5, 6])
.collect();
println!("Chained: {:?}", chained);
// enumerate, any, all, find, position
let found = v.iter().find(|&&x| x > 5);
let pos = v.iter().position(|&x| x == 7);
let all_pos = v.iter().all(|&x| x > 0);
let any_neg = v.iter().any(|&x| x < 0);
println!("Found: {:?}, Pos: {:?}, All>0: {all_pos}, Any<0: {any_neg}", found, pos);
}
Custom Iterators
struct Fibonacci {
a: u64,
b: u64,
}
impl Fibonacci {
fn new() -> Self {
Fibonacci { a: 0, b: 1 }
}
}
impl Iterator for Fibonacci {
type Item = u64;
fn next(&mut self) -> Option {
let result = self.a;
let new_b = self.a + self.b;
self.a = self.b;
self.b = new_b;
Some(result) // Infinite iterator
}
}
fn main() {
// Take first 10 Fibonacci numbers
let fibs: Vec = Fibonacci::new().take(10).collect();
println!("Fibonacci: {:?}", fibs);
// Sum of first 20 Fibonacci numbers
let sum: u64 = Fibonacci::new().take(20).sum();
println!("Sum of first 20 fibs: {sum}");
// First Fibonacci number over 1000
let big = Fibonacci::new().find(|&n| n > 1000);
println!("First fib > 1000: {:?}", big);
}
Key Takeaways
- ✅ Closures capture environment variables and implement Fn, FnMut, or FnOnce
- ✅ Use
moveto force ownership transfer into closures - ✅ Iterator adaptors (map, filter, fold) are lazy — they only execute when consumed
- ✅ Chaining iterators is idiomatic Rust and compiles to efficient machine code
- ✅ Implement the
Iteratortrait to create custom iteration logic