Understanding Closures in Rust

Understanding Closures in Rust

Like several other languages, Rust programming language supports closures also known as anonymous functions or lambda functions which is a powerful feature used for varying reasons depending on who you ask.

We’ll break down closures in Rust in this article, so you can start using it in your Rust code immediately and properly.

A closure in Rust is a function without a name that can be used as a variable. And there are three types of closures in Rust based on what they do and their return value.

Let’s first of all dissect a closure signature before discussing the different types of closures on Rust.

Closure signature

Let’s consider the signature below:


let closureName: [closure type] -> [return Type] = |argument1:[optional type], argument2:[optional type]| {x + y};  

The closure above shows a function that is assigned a name closureName with the closure type definition this is where the different types of closure comes in to play but we’ll see more about that as we proceed and then a return type, followed by two pipes || representing brackets () in in a conventional function which allows you to pass arguments to the closure. And finally, the closure body which is just after the second pipe. It also has an optional curly braces for instances where your closure body occupies multiple lines.

Here is an example of an actual closure that adds two numbers and returns the result of the addition:

let add: fn(i32, i32) -> i32 = |x: i32, y:i32| {x + y}; 

Closures are generally written within the scope of another function. So, let’s put the add closure in a main function and run it.

fn main(){

let add: fn(i32, i32) -> i32 = |x: i32, y:i32| {x + y}; 
println!("Result: {}", add(1,4));

}

Now, let’s take a look at the 3 different types of closures in Rust.

For every closure you create, the Rust compiler will categorise it into either of this 3 types based on what they do and the trait that is implemented for them other than that, that closure will be invalid. The traits that are possible to be implemented include:

First off, here is a summary from the Rust Documentation:

Fn is implemented automatically by closures which only take immutable references to captured variables or don’t capture anything at all, as well as (safe) function pointers (with some caveats, see their documentation for more details). Additionally, for any type F that implements Fn&F implements Fn, too.

Since both FnMut and FnOnce are supertraits of Fn, any instance of Fn can be used as a parameter where a FnMut or FnOnce is expected.

Use Fn as a bound when you want to accept a parameter of function-like type and need to call it repeatedly and without mutating state (e.g., when calling it concurrently). If you do not need such strict requirements, use FnMut or FnOnce as bounds.

FnFnMut and FnOnce

We’ve already seen an example of how to use Fn, you’ll typically want to use fn if you don’t intend to mutate the a captured variable in your closure. For example the code below contains a a closure with the variable name increment that captures a variable counter and is updating the counter variable each time the closure is called.

fn main() {
    let mut counter = 0;

    let increment = || {
        counter += 1;
        println!("Counter: {}", counter);
    };

    // Call the closure multiple times
    increment();
    increment();
    increment();
}

The code will return an error because of the update to the counter variable. So, we need to make the increment variable mutable. Which converts it to an fnMut closure — mutable closure.

Finally, the FnOnce closure. The fnOnce closure is automatically implemented for a closure that is allowed to be called only once. A closure is allowed to be called only once either because it was defined with a where clause to be a fnOnce function or it consumes its captured variable. Here is an example:

fn main() {
    let numbers = vec![1, 3, 4];
    
    let get_numbers = || { 
        numbers
    };

    println!("Numbers: {:?}", get_numbers());

    println!("Numbers 2: {:?}", get_numbers()); // this does not compile
}

In this example, the get_numbers closure consumes the variable numbers — took ownership and therefore makes it an FnOnce function and can only be called once. Any other call to the function will result to an error.

Conclusion

Closures in Rust are very interesting concepts and can be very useful in writing concise functions within a function. Understanding how it works will allow you work on projects like Leptos web framework that depends heavily on closures. I hope this article gave you a head-start, to learn more about closures in Rust, please, see the documentation.

Happy Hacking!

Buy Me A Coffee

Published by Eze Sunday Eze

Hi, welcome to my blog. I am Software Engineer and Technical Writer. And in this blog, I focus on sharing my views on the tech and tools I use. If you love my content and wish to stay in the loop, then by all means share this page and bookmark this website.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.