Rust Closures

What Are Closures in Rust?

Closures are anonymous functions you can define inline. Unlike regular functions, closures can capture and use variables from the scope in which they are defined. They are often used when you need to pass behavior as a value or create concise, reusable logic.

Syntax of Closures

The syntax of a closure is straightforward:

  • Use a pipe | symbol to enclose parameters.
  • Write the body of the closure after the parameters.
let closure_name = |parameters| expression;

Example 1: Simple Closure

fn main() {
let add = |a, b| a + b; // Closure to add two numbers
let result = add(5, 3);
println!("The sum is: {}", result);
}

Output:

The sum is: 8

Closures vs Functions

FeatureClosuresFunctions
DefinitionInline, anonymousDefined with fn keyword
Environment AccessCan capture variablesCannot access variables outside
Type InferenceParameters can be inferredParameters must be explicitly declared

Capturing the Environment

Closures in Rust can capture variables from their surrounding scope in three ways:

  1. By Borrowing (&T)
  2. By Mutably Borrowing (&mut T)
  3. By Taking Ownership (T)

Example 2: Capturing by Borrowing

fn main() {
let x = 10;
let print_x = || println!("x is: {}", x); // Borrowing x
print_x();
}

Output:

x is: 10

Example 3: Capturing by Mutably Borrowing

fn main() {
let mut x = 5;
let mut update_x = || x += 1; // Mutably borrowing x
update_x();
println!("Updated x: {}", x);
}

Output:

Updated x: 6

Example 4: Capturing by Taking Ownership

fn main() {
let x = String::from("Rust");
let consume_x = || println!("Captured value: {}", x); // Taking ownership
consume_x();
// println!("{}", x); // Error: x is moved
}

Output:

Captured value: Rust

Closures with Explicit Types

Rust can infer parameter and return types for closures, but you can also specify them explicitly if needed.

Example 5: Explicit Type Annotations

fn main() {
let multiply = |a: i32, b: i32| -> i32 { a * b };
let result = multiply(4, 5);
println!("The product is: {}", result);
}

Output:

The product is: 20

Closures and Iterators

Closures are often used with iterators to process collections concisely.

Example 6: Using Closures with Iterators

fn main() {
let numbers = vec![1, 2, 3, 4];
let squares: Vec<i32> = numbers.iter().map(|x| x * x).collect();
println!("Squares: {:?}", squares);
}

Output:

Squares: [1, 4, 9, 16]

Closures and move Keyword

The move keyword forces a closure to take ownership of the variables it captures.

Example 7: Using move with Closures

fn main() {
let x = String::from("Rust");
let print_x = move || println!("Owned value: {}", x);
print_x();
// println!("{}", x); // Error: x is moved
}

Output:

Owned value: Rust

Closures as Function Parameters

Closures can be passed to functions as arguments. You can use generics and the Fn traits (Fn, FnMut, FnOnce) to define such functions.

Example 8: Passing Closures to a Function

fn apply_closure<F>(f: F)
where
F: Fn(i32) -> i32,
{
let result = f(10);
println!("Result: {}", result);
}

fn main() {
let double = |x| x * 2;
apply_closure(double);
}

Output:

Result: 20

Common Traits for Closures

  1. Fn: The closure borrows values immutably.
  2. FnMut: The closure mutably borrows values.
  3. FnOnce: The closure takes ownership of the values.

Returning Closures

Closures can also be returned from functions using the impl keyword with the appropriate Fn trait.

Example 9: Returning a Closure

fn create_multiplier(multiplier: i32) -> impl Fn(i32) -> i32 {
move |x| x * multiplier
}

fn main() {
let multiply_by_3 = create_multiplier(3);
println!("3 * 4 = {}", multiply_by_3(4));
}

Output:

3 * 4 = 12

Leave a Comment

BoxofLearn