What are closures in Rust?

What Are Closures?

What are closures in Rust?

What Are Closures?

A closure, also known as an anonymous function or lambda expression, is a function defined within the context of its usage. They allow functions to be defined within the scope of another function and can capture variables from their surrounding scope.

In Rust, closures are defined using a pair of vertical bars (||), which act like the brackets of a function. Between these bars, we specify the arguments to the function, followed by the code block that constitutes the body of the function. Let's look at a basic example:

let add_one = |x| x + 1; 
println!("{}", add_one(5));  // Output: 6

Here, add_one is a closure that takes one argument, x, and returns x + 1.

Capturing Variables

A strong feature of closures is their ability to capture variables from their surrounding environment, commonly called upvar capturing. Variables can be captured in three ways:

  1. By reference: &T
  2. By mutable reference: &mut T
  3. By value: T

Rust will try to infer the least restrictive choice based on how you use the variables within the closure. Let's demonstrate this:

let x = 7; 
 
let borrow = || println!("borrow: {}", x); 
let borrow_mut = || println!("borrow_mut: {}", x); 
let move = move || println!("move: {}", x); 
 
borrow(); 
borrow_mut(); 
move();

In this example, borrow captures x by reference (&T), borrow_mut captures x by mutable reference (&mut T), and move captures x by value (T). The move keyword is used to force the closure to take ownership of the values it's using.

Type Inference and Genericity

Rust's closure implementation shines when it comes to type inference and genericity. While function definitions require explicit types for their arguments, Rust closures do not. Additionally, unlike functions, closures can be generic over their input types. Here's an example:

let example_closure = |x| x; 
 
let s = example_closure(String::from("hello")); 
let n = example_closure(5);

In this code, example_closure can take either a String or an i32 because it is generic.

Fn, FnMut, and FnOnce

In Rust, we have three traits to represent three kinds of closures: Fn, FnMut, and FnOnce, which correspond to the three ways of capturing variables:

  1. Fn takes variables by reference (&T)
  2. FnMut takes variables by mutable reference (&mut T)
  3. FnOnce takes variables by value (T)

Each closure implements one or more of these traits. If a closure implements Fn, then it also implements FnMut and FnOnce. If it implements FnMut, it also implements FnOnce.

let x = 7; 
 
let fn_closure = || println!("{}", x);  // Implements Fn 
let mut fnmut_closure = || println!("{}", x);  // Implements FnMut 
let fnonce_closure = move || println!("{}", x);  // Implements FnOnce

Practical Uses of Closures

There are many ways to use closures in Rust to write concise and flexible code. Let's discuss some of them:

As Arguments to Functions

Functions in Rust can take closures as arguments. This is often used in higher-order functions, which take other functions as arguments or return them as results.

Here's an example where we use a closure to implement a map function, similar to the map function on iterators:

fn map<F>(value: i32, func: F) -> i32 
where 
    F: Fn(i32) -> i32, 
{ 
    func(value) 
} 
 
let double = |x| x * 2; 
println!("{}", map(5, double)); // Output: 10

In this example, map takes an i32 and a closure as arguments applies the closure to the value and returns the result.

Closures in Data Structures

Storing a closure in a struct for later execution is a typical pattern in Rust, particularly in event-driven or callback-driven designs.

struct Button<F> { 
    callback: F, 
} 
 
impl<F: Fn()> Button<F> { 
    fn new(callback: F) -> Self { 
        Self { callback } 
    } 
 
    fn click(&self) { 
        (self.callback)(); 
    } 
} 
 
let button = Button::new(|| println!("Button clicked!")); 
button.click();  // Output: Button clicked!

In this example, we create a Button struct that stores a closure as a callback. When the button is clicked, the callback is invoked.

Closures with Iterators

Closures are used extensively with iterators in Rust. They provide a concise way to perform operations on each collection element. For example:

let numbers = vec![1, 2, 3, 4, 5]; 
let squares: Vec<_> = numbers.iter().map(|x| x * x).collect(); 
println!("{:?}", squares);  // Output: [1, 4, 9, 16, 25]

In this example, the closure |x| x * x is passed to the map method of the iterator, which applies the closure to each element.

Check out more articles about Rust in my Rust Programming Library!

Conclusion

Rust's closures are an exciting feature allowing flexibility and conciseness in your code. They capture their environment flexibly, can be used similarly to functions, and bring advantages like catching scope and type inference. They can be used as function parameters, stored in data structures for later use, and paired with iterators for efficient data manipulation.

Stay tuned, and happy coding!

Check out more articles about Rust in my Rust Programming Library!

Visit my Blog for more articles, news, and software engineering stuff!

Follow me on Medium, LinkedIn, and Twitter.

Check out my most recent book — Application Security: A Quick Reference to the Building Blocks of Secure Software.

All the best,

Luis Soares

CTO | Head of Engineering | Blockchain Engineer | Solidity | Rust | Smart Contracts | Web3 | Cyber Security

#blockchain #rust #programming #language #traits #abstract #polymorphism #smartcontracts #network #datastructures #data #smartcontracts #web3 #security #privacy #confidentiality #cryptography #softwareengineering #softwaredevelopment #coding #software

Read more