Rust Closures will make your life easy

Reading Time: 4 minutes

Rust closures are a very helpful and efficient feature of Rust. Closures are quite useful as it allows environment capturing. Let’s explore it together and learn more about it.

Introduction

Rust closures are anonymous functions without any name that you can save in a variable or pass as arguments to other functions. Unlike functions, closures can capture values from the scope in which they’re defined.

Example – 

let closure_example = |num| -> i32 { num + 1 };

It is a simple example of closure. All the thing on the right side of equals is a closure and it is stored in a variable named closure_example. Now let us understand the syntax of closure.

Syntax of Closures

The syntax of closure is quite simple and it looks similar to that of a function. Here is the syntax of a closure.

let closure_example = |param1, param2| -> return_type {..... body …..};

|| contains parameters that will be passed to the closure similar to functions.

return_type is the data type returned by the closure.

{} contains the body of the closure.

Type Inference in Closures

In functions, we need to specify the data type of arguments passed and also the return data type, but that is not the case with closures. Unlike functions, Closure does not need type inferences. The compiler implicitly infers types.

fn  add_one (num: u32) -> u32 { num + 1 } 

Here is an example of a function and as you can see, we need to define the data types of arguments and return data types. But if we define a closure for the same it will look something like this

let add_one  = |num| { num + 1 };     

We can define the data types in closures but it is not necessary. Everything works just fine without it.

Now let us see another aspect of type inference. Here is an example of code. It looks fine at first sight but when we compile it, we see a compile error.

fn main() {
    let closure = |num| num;
    let var_1 = closure(5);
    let var_2 = closure(2.5);
}

This is the error that we get.

We get this error because when the first time we called our closure with an integer as a parameter, the Rust compiler inferred the type of num to be an integer. So, when we call it again, it expects an integer but gets a floating-point number.

Using closures inside structures

We can also use a closure as a member of a structure in Rust. Rust structures need to know the type of all of their members. So, if we want to make a closure a part of the struct then we need to specify its type. To do so we take the help of these three traits – Fn, FnMut, FnOnce.

All closures in rust need to implement at least one of these traits. So we can use this to specify the type of closure in the struct.

Here is an example

struct Example<T>
where
    T: Fn(u32) -> u32,
{
    closure: T,
    value: Option<u32>,
}

This is a struct that is generic over T and is bound by Fn trait. Now we can define a member in this structure of type T to make it a closure.

Environment Capturing

One of the most important use of closure is that it can capture the environment. This means it can use the values from the scope it is in.

fn main() {
    let num = 4;
    let print_num = || print!("{}",num); 
    print_num(); 
}

The above code shows us how the closure print_num uses the value to num which was not defined in it. This is environment capturing. It is a useful feature in defining iterators.

Move keyword in Closures

As discussed earlier, all closures must implement at least one of the following traits – Fn, FnMut, & FnOnce.

Rust infers which trait to use based on how the closure uses the values from the environment.

  • FnOnce: The closure takes ownership of the same variables only once.
  • FnMut: The closure mutably borrows values.
  • Fn: The closure borrows values from the environment immutably.

To force a closure to take ownership of a variable we use move keyword.

This is how we can use move key word.

fn main() {
    let numbers = vec![1, 2, 3];
    let equal_to_numbers = move |vec| vec == numbers
    println!("can't use x here: {:?}", numbers);
    let vec_to_compare = vec![1, 2, 3];
    assert!(equal_to_numbers(vec_to_compare));
}

When we compile it, we get this error.

We get an error if we compile the above code as the closure takes ownership of the numbers vector. Therefore, we cannot print it because the main function is not the owner of numbers anymore.

This was all about Rust closures.

If you want to read more content like this?  Subscribe to Rust Times Newsletter and receive insights and latest updates, bi-weekly, straight into your inbox. Subscribe to Rust Times Newsletter: https://bit.ly/2Vdlld7.


Knoldus-blog-footer-image

1 thought on “Rust Closures will make your life easy4 min read

Comments are closed.