当前位置:网站首页>Closure type of rust (difference between FN, fnmut and fnone)

Closure type of rust (difference between FN, fnmut and fnone)

2022-04-23 18:04:00 Xu Yeping

Rust Closure type of (Fn, FnMut, FnOne The difference between )

1 What is a closure ?

Let's take a look at the description on Wikipedia :

In computer science , Closure ( English :Closure), Also known as lexical closure (Lexical Closure) Or function closure (function closures), It's a function that references a free variable . This referenced free variable will exist with this function , Even if it has left the environment in which it was created . therefore , Another way of saying is that a closure is an entity composed of a function and its associated reference environment . Closures can have multiple instances at run time , Different reference environments and the same combination of functions can produce different instances .

The concept of closure appears in 60 years , The first programming language to implement closures was Scheme. after , Closures are widely used in functional programming languages such as ML Language and LISP. Many imperative programming languages have also begun to support closures .

You can see , The first sentence has explained what a closure is : A closure is a function that references a free variable . therefore , Closure is a special function .

stay Rust in , Closures are divided into three types , List the following

  • Fn(&self)
  • FnMut(&mut self)
  • FnOnce(self)

stay rust in , Functions and closures are implemented FnFnMut or FnOnce Trait (trait) The type of . Any type of object that implements one of these three qualities , All are Callable object , Can pass like functions and closures name() Form call of ,() stay rust Is an operator , Operator in rust Can be overloaded in .rust Operator overloading is achieved by implementing the corresponding trait To achieve , and () The corresponding of the operator trait Namely FnFnMut and FnOnce, therefore , Any implementation of these three trait One of the types of , In fact, it's overloaded () The operator .

2 Rust Definitions of three closures in :

 Insert picture description here

2.1 FnOnce

  • Standard library definition
#[lang = "fn_once"]
pub trait FnOnce<Args> {
    
    type Output;
    extern "rust-call" fn call_once(self, args: Args) -> Self::Output;
}

Parameter type is self, therefore , This type of closure takes ownership of variables , A lifecycle can only be the current scope , Then it will be released .

  • FnOnce Example :
#[derive(Debug)]
struct E {
    
    a: String,
}

impl Drop for E {
    
    fn drop(&mut self) {
    
        println!("destroyed struct E");
    }
}

fn fn_once<F>(func: F) where F: FnOnce() {
    
    println!("fn_once begins");
    func();
    println!("fn_once ended");
}

fn main() {
    
    let e = E {
     a: "fn_once".to_string() };
    //  Add one like this move, See how the output order of program execution is different 
    // let f = move || println!("fn once calls: {:?}", e);
    let f = || println!("fn once closure calls: {:?}", e);
    fn_once(f);
    println!("main ended");
}

The results are as follows :

fn_once begins
fn once closure calls: E {
     a: "fn_once" }
fn_once ended
main ended
destroyed struct E
  • FnOnce Type closure -Rust playground Example

But if the closure runs twice , such as :

fn fn_once<F>(func: F) where F: FnOnce() {
    
    println!("fn_once begins");
    func();
    func();
    println!("fn_once ended");
}

The compiler will report an error , Like this :

error[E0382]: use of moved value: `func`
  --> src/main.rs:15:5
   |
12 | fn fn_once<F>(func: F) where F: FnOnce() {
    
   |            -  ---- move occurs because `func` has type `F`, which does not implement the `Copy` trait
   |            |
   |            consider adding a `Copy` constraint to this type argument
13 |     println!("fn_once begins");
14 |     func();
   |     ---- value moved here
15 |     func();
   |     ^^^^ value used here after move

error: aborting due to previous error

FnOnce Type closure error -Rust playgroound Example

Why is that ?

Or go back FnOnce The definition of , Parameter type is self, So in func After the first execution , The variables that were captured before were released , So it's impossible to perform the second time . therefore , If you want to run multiple times , have access to FnMut\Fn.

2.2 FnMut

  • Standard library definition
#[lang = "fn_mut"]
pub trait FnMut<Args>: FnOnce<Args> {
    
    extern "rust-call" fn call_mut(&mut self, args: Args) -> Self::Output;
}

Parameter type is &mut self, therefore , This type of closure is mutable , Will change the variable , But the variable will not be released . So you can run multiple times .

  • FnMut Example
    Similar to the above example , There are two places to change :
fn fn_mut<F>(mut func: F) where F: FnMut() {
    
    func();
    func();
}

// ...
let mut e = E {
     a: "fn_once".to_string() };
let f = || {
     println!("FnMut closure calls: {:?}", e); e.a = "fn_mut".to_string(); };
// ...

The results are as follows :

fn_mut begins
fn mut closure calls: E {
     a: "fn_mut" }
fn mut closure calls: E {
     a: "fn_mut" }
fn_mut ended
main ended
destroyed struct E

FnMut Type closure -Rust playground Example

It can be seen that FnMut Closures of types can be run multiple times , And you can modify the value of the capture variable .

2.3 Fn

  • Standard library definition
#[lang = "fn"]
pub trait Fn<Args>: FnMut<Args> {
    
    extern "rust-call" fn call(&self, args: Args) -> Self::Output;
}

Parameter type is &self, therefore , This type of closure is immutable , Does not change variables , This variable will not be released . So you can run multiple times .

  • Fn Example
fn fn_immut<F>(func: F) where F: Fn() {
    
    func();
    func();
}

// ...
let e = E {
     a: "fn".to_string() };
let f = || {
     println!("Fn closure calls: {:?}", e); };
fn_immut(f);
// ...

The results are as follows :

fn_imut begins
fn closure calls: E {
     a: "fn" }
fn closure calls: E {
     a: "fn" }
fn_imut ended
main ended
destroyed struct E

It can be seen that Fn Closures of types can be run multiple times , But you can't modify the value of the capture variable .

Fn Type closure -Rust playground Example

3 Common mistakes

Sometimes in use Fn/FnMut Here is the type of closure , Compilers often give such errors :

# ...
cannot move out of captured variable in an Fn(FnMut) closure
# ...

See how to reproduce this situation :

fn main() {
    
    fn fn_immut<F>(f: F) where F: Fn() -> String {
    
        println!("calling Fn closure from fn, {}", f());
    }

    let a = "Fn".to_string();
    fn_immut(|| a); //  The closure returns a string 
}

In this way, there will be the above error . But how to fix it ?

fn_immut(|| a.clone());

But what is the reason ?

Just change the above code a little , To run a , The error given by the compiler is obvious :

fn main() {
    
    fn fn_immut<F>(f: F) where F: Fn() -> String {
    
        println!("calling Fn closure from fn, {}", f());
    }

    let a = "Fn".to_string();
    let f = || a;
    fn_immut(f);
}

The error given by the compiler is as follows :

7 |     let f = move || a;
  |             ^^^^^^^^-
  |             |       |
  |             |       closure is `FnOnce` because it moves the variable `a` out of its environment
  |             this closure implements `FnOnce`, not `Fn`
8 |     fn_immut(f);
  |     -------- the requirement to implement `Fn` derives from here

You see , The compiler deduces that the closure is FnOnce Type of , Because the closure finally returns a, Returned ownership , You can't run it a second time , Because closures are no longer a Owner .

and Fn/FnMut It is recognized that it can run multiple times , If the ownership of the captured variable is returned , Then it won't run next time , So it will report the previous error .

4 Conclusion

Because closures and rust Life cycle in , Ownership is closely linked , Sometimes it's hard to understand , But write more code , Try a few times more , You can probably understand the difference between the three .

All in all , Closure is rust Very easy to use functions in , It can make the code concise and elegant , It is worth learning and mastering !


Link to the original text :https://www.cnblogs.com/dream397/p/14190206.html
Original author :tycoon3

版权声明
本文为[Xu Yeping]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/04/202204230544498298.html