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 typeF
that implementsFn
,&F
implementsFn
, too.Since both
FnMut
andFnOnce
are supertraits ofFn
, any instance ofFn
can be used as a parameter where aFnMut
orFnOnce
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, useFnMut
orFnOnce
as bounds.
Fn, FnMut 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!