Rust basic notes: closures

Time:2021-12-2

grammar

Closure looks like this:

    let plus_one = |x: i32| x + 1;
    assert_eq!(2, plus_one(1));

First create a binding plus_ One, and then assign it to a closure. The body is an expression. Note that {} is also an expression.

It can also be written like this:

    let plus_two = |x| {
        let mut result: i32 = x;
    
        result += 1;
        result += 1;
    
        result
    };
    assert_eq!(4, plus_two(2));
    

Compared with the conventional function definition, the difference is that the keyword FN is not used in the closure. Here is the difference:

fn  plus_one_v1   (x: i32) -> i32 { x + 1 }
let plus_one_v2 = |x: i32| -> i32 { x + 1 };
let plus_one_v3 = |x: i32|          x + 1  ;

It should be noted that the types of parameters and return values in the closure can be omitted, and the following forms are also possible:

let plus_one = |x| x + 1;

Closure and its environment

A small example:

    let num = 5;
    let plus_num = |x: i32| x + num;
    
    assert_eq!(10, plus_num(5));
    

That is, plus_ Num refers to a variable num in its function. Specifically, it is a borrow, which meets the requirements of the ownership system. Let’s take a wrong example:

let mut num = 5;
let plus_num = |x: i32| x + num;

let y = &mut num;

error: cannot borrow `num` as mutable because it is also borrowed as immutable
    let y = &mut num;
                 ^~~

In the code above, plus_ Num has made an immutable reference to num, and in plus_ Another variable reference occurs within the scope of one, so the following rules in the ownership system are violated:

If an immutable reference is made to a binding, the immutable reference cannot be made until the reference does not exceed the scope, and vice versa.

Modify the code as follows:

    let mut num = 5;
    {
        let plus_num = |x: i32| x + num;
    
    } // plus_num goes out of scope, borrow of num ends
    
    let y = &mut num;

Take another example:

    let nums = vec![1, 2, 3];
    let takes_nums = || nums;
    println!("{:?}", nums);
    

Any problems?
Yes, and it’s a big problem. The error reported by the compiler is as follows:


closure.rs:8:19: 8:23 error: use of moved value: `nums` [E0382]
closure.rs:8    println!("{:?}", nums);

It can be seen from the error that in the last output statement, Num has no VEC on the resource! The ownership of [1, 2, 3] has been moved to the closure.

So here comes the question…:

Why in the previous example, the closure is a row, and here it becomes a move?

Let’s start from scratch:

    let mut num = 5;
    let plus_num = || num + 1;
    let num2 = &mut num;
    
Error:
closure.rs:5:21: 5:24 error: cannot borrow `num` as mutable because it is also borrowed as immutable
closure.rs:5     let num2 = &mut num;

Explain that immutable arrow occurs in the closure, which will conflict with the following &mut. Now let’s make a change:

let plus_num = || num + 1; 
    //Change to the following statement
    let mut plue_num = || num += 1;

Compile again:

Error:
closure.rs:4:17: 4:20 error: cannot borrow `num` as mutable more than once at a time
closure.rs:4 let num2 = &mut num;

It can be found that mutable arrow occurs in the closure. Why is this?

The closure is nothing more than these three cases:

  • by reference: &T

  • by mutable reference: &mut T

  • by value: T

    Which of the three depends on how you use the inside of the closure, and then the compiler automatically infers whether the binding type is fn() fnmut() or fnonce()

let plus_ num = || num + 1;         //  This only needs to be referenced, so plus_ Num type is FN ()
    let mut plue_ num = || num += 1;    //  This requires & mut T, so plus_ Num type is fnmut ()
    //This is an example in the manual
    //This is a type that does not implement copy trait
    let movable = Box::new(3);
    //'drop 'requires type T, so the closure environment requires by value T., so the consumption type is fnonce()
    let consume = || {
        drop(movable);    //  Move happened here
    };
    //So this consumer can only be executed once
    consume();

One thing to note is:
The previous examples should be divided into two categories:

  1. let a= 100i32;

  2. let a = vec![1,2,3];

The difference is that I32 type implements copy trait, while vector does not!!!

reference resources:http://rustbyexample.com/fn/closures/capture.html

Move closure

Use the move keyword to force the closure to obtain ownership, but pay attention to the following examples:

    let num = 5;
    let owns_num = move |x: i32| x + num;

Although move is used here and the variable follows the move semantics, here5Copy is implemented, so owns_ What’s the difference between the ownership of 5 copies obtained by own?
Take a look at this Code:

    let mut num = 5;
    {
        let mut add_num = |x: i32| num += x;
        add_num(5);
    }
    assert_eq!(10, num);    

This code gets the result we want, but what if we add the move keyword? The above code will report an error because the value of num is still 5 and has not changed,

Why?
As mentioned above, move forces the closure environment to obtain ownership, but 5 implements copy, so the closure obtains the ownership of its copy. Similarly, what is modified in the closure is also the copy of 5.

summary

In rust, the concept of closure is not easy to understand, because too many concepts of ownership are involved. You can understand the ownership first, and the closure is easy to understand.