What Are the Closures In Rust?

Closures are short anonymous functions in Rust. It is not declared with a name like a normal function.

Closures are a powerful and flexible feature because they allow writing logic quickly without needing a full function.

Closures are generally used for short logic, not long code blocks. For example, one-liners or small blocks that are useful and reusable.

let square = |x| x * x;
println!("{}", square(4)); // Output: 16

It is a capture variable from their surrounding environment. This means closures can use variables that are defined outside the closure. For example:

fn main() {
let name = "Rusty";

let greet = || {
println!("Hello, {}!", name); // Uses `name` from the surrounding scope
};

greet(); // Output: Hello, Rusty!
}

Normal function can’t do this unless the variable is passed in. But closures remember the values around them.

Basic Closure Syntax:

let closure_name = |parameter1, parameter2| {
// logic
};

In simple terms, when you write a normal function in Rust, you have to pass all the values it needs through parameters.

But when you write a closure, it can automatically “see” and use variables that were defined outside of it without passing them manually.

Here, we provide both comparison examples:

a) Regular Function:

fn add_bonus(score: i32, bonus: i32) -> i32 {
score + bonus
}

fn main() {
let result = add_bonus(90, 10); // Must pass bonus as argument
println!("Final Score: {}", result);
}

b) Closure:

fn main() {
let bonus = 10;

let add_bonus = |score: i32| score + bonus;

let result = add_bonus(90); // No need to pass bonus
println!("Final Score: {}", result);
}

Now, you can compare both differences yourself.

Example 1: A Simple Adder Closure

fn main() {
let add = |a: i32, b: i32| a + b;

let result = add(3, 4);
println!("Sum is: {}", result);
}

Output:

Sum is: 7

This closure takes two integers and returns their sum, just like a short inline function.

Example 2: Greeting Generator

fn main() {
let name = "Zohn";

let greet = || println!("Hello, {}!", name);

greet(); // name is captured from environment
}

Output:

Hello, Ravi!

You can see, the closure captures the variable name without explicitly passing it as a parameter.

Example 3: Filtering List with Closure

fn main() {
let numbers = vec![1, 2, 3, 4, 5];

let even_numbers: Vec<i32> = numbers.into_iter()
.filter(|x| x % 2 == 0)
.collect();

println!("Even numbers: {:?}", even_numbers);
}

Output:

Even numbers: [2, 4]

In this example, the filter method accepts a closure that determines which values to keep.

Types of Closures in Rust

  • Fn
  • FnMut
  • FnOnce

1) Fn (Reads only)

This closure can read only a variable. Don’t do anything, change and delete itself. only borrow the variable without changes.

For example:

fn main() {
let name = String::from("Rust");

let greet = || println!("Hello, {}", name); // just reads `name`

greet(); // works fine
}

In this example, Closure reads the variable -> captured as an immutable reference.

b) FnMut (Modifies variable)

This closure can change a variable, like borrowing the variable mutably, and you should have to mut the variable.

fn main() {
let mut count = 0;

let mut add_one = || count += 1; // modifies `count`

add_one(); // works because it's mut
println!("Count: {}", count);
}

Closure modifies the variable -> captured as a mutable reference. It will give a compile-time error if you miss the Mut.

example of without Mut:

let count = 0;

let mut increase = || {
count += 1;
};

This will give you an error because we don’t add a mut in the count variable.

c) FnOnce (Takes ownership)

This closure takes the variable ownership. It means you can not use that variable after closure, and it will run only once; that’s what it is called “FnOnce”.

fn main() {
let message = String::from("Goodbye!");

let consume = || drop(message); // takes ownership of `message`

consume(); // works once
// println!("{}", message); ERROR: message moved
}

What is Move Keyword in Closure?

When you use the move keyword before a closure, you are telling Rust to take ownership of the outer variables used inside the closure. This means the closure moves those values inside itself.

For example:

fn consume_closure(f: impl FnOnce()) {
f();
}

fn main() {
let name = String::from("Roy");

let closure = move || {
println!("Goodbye, {name}"); // Ownership of `name` moved here
};

consume_closure(closure); // This works, because closure is FnOnce
}

You cannot call this closure again because it moved “name” inside, and a String cannot be copied.

How To Returning Closures from Functions?

We can return closures from functions using the impl Fn syntax.

fn make_multiplier(factor: i32) -> impl Fn(i32) -> i32 {
move |x| x * factor
}

fn main() {
let times3 = make_multiplier(3);
println!("3 x 4 = {}", times3(4));
}

Output:

3 x 4 = 12

Normally, a function can return like a number = i32, a string = String, but sometimes, we want to return a closure from another function.

  • impl Fn( . . . ) = This function will return something that behaves like a closure that can be called, but I don’t want to write its exact complex type. It’s avoid writing messy closure types.

Exercise Task For Practice:

Write a closure that takes a name and prints a welcome message.
Then, write a second closure that checks whether a number is divisible by 3 or not.

Try this exercise without any help to understand closures.

Learn other Topics About Rust Programming

Leave a Comment