Rust Lesson 29 – Closures | Dataplexa

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 immutably
  • FnMut — borrows values mutably
  • FnOnce — 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.