Closures#
- Source:
Rust closures are anonymous functions that can capture variables from their environment. They’re similar to C++ lambdas but with automatic capture mode inference.
Basic Syntax#
C++:
#include <functional>
int main() {
// Lambda with explicit capture
int x = 10;
auto add = [x](int y) { return x + y; };
auto result = add(5); // 15
// Mutable capture
int count = 0;
auto increment = [&count]() { count++; };
}
Rust:
fn main() {
// Closure with automatic capture
let x = 10;
let add = |y| x + y;
let result = add(5); // 15
// Mutable capture
let mut count = 0;
let mut increment = || count += 1;
increment();
}
Capture Modes#
C++ explicit captures:
int a = 1, b = 2;
auto by_value = [a, b]() { return a + b; };
auto by_ref = [&a, &b]() { return a + b; };
auto all_value = [=]() { return a + b; };
auto all_ref = [&]() { return a + b; };
auto mixed = [a, &b]() { return a + b; };
Rust automatic inference:
let a = 1;
let mut b = 2;
// Rust infers capture mode based on usage
let by_ref = || a + b; // borrows a and b
let by_mut_ref = || { b += 1; }; // mutably borrows b
// Force move with `move` keyword
let by_value = move || a + b; // moves/copies a and b
Move Closures#
fn main() {
let s = String::from("hello");
// Without move: borrows s
let borrow = || println!("{}", s);
// With move: takes ownership of s
let own = move || println!("{}", s);
// s is no longer valid here
// Essential for threads
let data = vec![1, 2, 3];
std::thread::spawn(move || {
println!("{:?}", data);
});
}
Fn Traits#
Rust closures implement one or more of three traits:
Trait |
Receiver |
Can be called |
|---|---|---|
|
|
Once (consumes captured values) |
|
|
Multiple times (may mutate) |
|
|
Multiple times (no mutation) |
// FnOnce - consumes captured value
let s = String::from("hello");
let consume = || drop(s); // can only call once
// FnMut - mutates captured value
let mut count = 0;
let mut increment = || count += 1; // can call multiple times
// Fn - only reads captured values
let x = 10;
let read = || x + 1; // can call multiple times
Closures as Parameters#
C++:
#include <functional>
void apply(std::function<int(int)> f, int x) {
std::cout << f(x);
}
// Or with templates
template<typename F>
void apply_template(F f, int x) {
std::cout << f(x);
}
Rust:
// Generic with trait bound (monomorphized, like C++ template)
fn apply<F>(f: F, x: i32) -> i32
where
F: Fn(i32) -> i32,
{
f(x)
}
// impl Trait syntax (simpler)
fn apply_impl(f: impl Fn(i32) -> i32, x: i32) -> i32 {
f(x)
}
// Dynamic dispatch (like std::function)
fn apply_dyn(f: &dyn Fn(i32) -> i32, x: i32) -> i32 {
f(x)
}
fn main() {
let double = |x| x * 2;
println!("{}", apply(double, 5)); // 10
}
Returning Closures#
C++:
std::function<int(int)> make_adder(int x) {
return [x](int y) { return x + y; };
}
Rust:
// Return with impl Trait
fn make_adder(x: i32) -> impl Fn(i32) -> i32 {
move |y| x + y
}
// Return boxed closure (for dynamic dispatch)
fn make_adder_boxed(x: i32) -> Box<dyn Fn(i32) -> i32> {
Box::new(move |y| x + y)
}
fn main() {
let add5 = make_adder(5);
println!("{}", add5(10)); // 15
}
Closure Type Inference#
Rust infers closure parameter and return types from usage:
// Types inferred from usage
let add = |a, b| a + b;
let result: i32 = add(1, 2); // infers i32
// Explicit types when needed
let parse = |s: &str| -> i32 { s.parse().unwrap() };
// Once inferred, types are fixed
let identity = |x| x;
let s = identity("hello"); // infers &str
// let n = identity(42); // error: expected &str
Closures with Iterators#
let numbers = vec![1, 2, 3, 4, 5];
// map
let doubled: Vec<_> = numbers.iter().map(|x| x * 2).collect();
// filter
let evens: Vec<_> = numbers.iter().filter(|&&x| x % 2 == 0).collect();
// fold
let sum = numbers.iter().fold(0, |acc, x| acc + x);
// for_each
numbers.iter().for_each(|x| println!("{}", x));
// find
let first_even = numbers.iter().find(|&&x| x % 2 == 0);
See Also#
Iterators - Closures with iterator adapters
Traits and Generics - Fn traits in detail