Closures in Rust
In this lesson, you will learn about closures in Rust. Closures are anonymous functions that can capture values from their surrounding environment.
Closures are commonly used with iterators, callbacks, and functional-style programming, making Rust code concise and expressive.
What Is a Closure?
A closure is a function-like construct that:
- Does not require a name
- Can capture variables from its environment
- Can be stored in variables or passed to functions
Closures use the | | syntax to define parameters.
Basic Closure Example
Here is a simple closure that adds two numbers:
let add = |a, b| a + b;
println!("{}", add(2, 3));
The closure automatically infers parameter and return types.
Closures vs Functions
Closures and functions are similar, but closures can capture values from their surrounding scope.
let x = 10;
let print_x = || {
println!("{}", x);
};
print_x();
Here, the closure uses the variable x from outside its body.
Capturing Variables
Closures can capture variables in three ways:
- By reference (
&T) - By mutable reference (
&mut T) - By value (ownership)
Rust determines how to capture variables automatically.
Mutable Closures
If a closure modifies a captured variable, the variable must be mutable.
let mut count = 0;
let mut increment = || {
count += 1;
};
increment();
increment();
println!("{}", count);
Closures Taking Ownership
Closures can take ownership of variables using the move keyword.
let name = String::from("Rust");
let consume = move || {
println!("{}", name);
};
consume();
After this, name cannot be used elsewhere.
Closure Traits: Fn, FnMut, FnOnce
Closures implement one or more of the following traits:
Fn— borrows values immutablyFnMut— borrows values mutablyFnOnce— takes ownership of values
Which trait is used depends on how the closure captures variables.
Closures with Iterators
Closures are heavily used with iterator methods like map and filter.
let nums = vec![1, 2, 3, 4];
let squared: Vec = nums.iter()
.map(|x| x * x)
.collect();
println!("{:?}", squared);
Closures as Function Parameters
Functions can accept closures using generic trait bounds.
fn apply(func: F)
where
F: Fn(),
{
func();
}
Why Closures Are Important
Closures allow you to:
- Write concise logic
- Capture context safely
- Use functional programming patterns
- Work seamlessly with iterators
📝 Practice Exercises
Exercise 1
Create a closure that multiplies two numbers.
Exercise 2
Create a closure that prints a captured variable.
Exercise 3
Use a closure with filter() to keep even numbers.
Exercise 4
Create a closure that takes ownership of a string.
✅ Practice Answers
Answer 1
let multiply = |a, b| a * b;
Answer 2
let msg = "Hello";
let print = || println!("{}", msg);
print();
Answer 3
let evens: Vec = nums.iter()
.filter(|x| *x % 2 == 0)
.collect();
Answer 4
let text = String::from("Rust");
let take = move || {
println!("{}", text);
};
What’s Next?
In the next lesson, you will learn about Modules in Rust — how Rust organizes code across files and projects.