Rust里的Fn/FnMut/FnOnce和闭包匿名函数关系

发布于:2024-05-06 ⋅ 阅读:(15) ⋅ 点赞:(0)

闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例。

什么是闭包:闭包是引用了自由变量的函数。所以,闭包是一种特殊的函数。

在 Rust 中,Fn、FnMut 和 FnOnce 是三个用于表示闭包类型的 trait,每一个闭包都是实现了其中一个特性。闭包是一种可以捕获其环境变量的函数。在创建闭包是会默认实现这几个 trait 中的一个。

以下是三个 trait 的区别

Fn:Fn 是最基本的闭包 trait。它表示闭包可以捕获其环境变量的不可变引用。

FnMut:FnMut 表示闭包可以捕获其环境变量的可变引用。这意味着闭包可以修改其环境变量的值。

FnOnce:FnOnce 表示闭包只能调用一次。它表示闭包可以捕获其环境变量的所有权。这意味着闭包可以移动其环境变量的值。

先看function.rs源码:

pub trait FnOnce<Args: Tuple> {
    /// The returned type after the call operator is used.
    #[lang = "fn_once_output"]
    #[stable(feature = "fn_once_output", since = "1.12.0")]
    type Output;

    /// Performs the call operation.
    #[unstable(feature = "fn_traits", issue = "29625")]
    extern "rust-call" fn call_once(self, args: Args) -> Self::Output;
}

pub trait FnMut<Args: Tuple>: FnOnce<Args> {
    /// Performs the call operation.
    #[unstable(feature = "fn_traits", issue = "29625")]
    extern "rust-call" fn call_mut(&mut self, args: Args) -> Self::Output;
}

pub trait Fn<Args: Tuple>: FnMut<Args> {
    /// Performs the call operation.
    #[unstable(feature = "fn_traits", issue = "29625")]
    extern "rust-call" fn call(&self, args: Args) -> Self::Output;
}

也就是说实现FnMut的闭包肯定也实现了FnOnce;实现Fn的闭包同时肯定也实现了FnMut和FnOnce.

另外,从以上代码,我们还能这么理解:闭包可以看成一个有call方法的结构体。

现在,来看看rust圣经的3句话:

  • 所有的闭包都自动实现了 FnOnce 特征,因此任何一个闭包都至少可以被调用一次
  • 没有移出所捕获变量的所有权的闭包自动实现了 FnMut 特征
  • 不需要对捕获变量进行改变的闭包自动实现了 Fn 特征

第一句没啥疑问,因为它是继承链的顶端,显然,所有闭包都实现了FnOnce

第二句,有些不明所以,先放着。

第三句,因为“不需要对捕获变量进行改变”,可以理解为call(&self,所以规则上实现Fn没啥问题。

再看几个例子

为方便演示,我们定义几个函数:

fn exec_once<F: FnOnce()>(f: F){
    f();
}

fn exec_mut_fn<F: FnMut()>(mut mut_f: F){
    mut_f();
}

fn exec_fn<F: Fn()>(f: F){
    f();
}

依次用来执行实现各种Trait的闭包

例1,move了环境变量的闭包:

fn main() {
    let mut s = ">> ".to_string();
    let move_f = || println!("{}", s + " world");
    exec_once(move_f);
    //failed:
    // exec_fn(move_f);
    //failed:
    // exec_mut_fn(move_f);
}

例2:可变借用了环境变量的闭包(省略main):

    let mut_f = || { 
        s.push_str("hello");
        println!("{}", s);
    };
    exec_mut_fn(mut_f);
    // or:
    // exec_once(mut_f);

例3:不可变借用了环境变量的闭包:

    let f =  || println!("{}", s.len());
    exec_fn(f);
    //or
    //exec_mut_fn(f);
    //or
    //exec_once(f);

上面3个例子很好地解释了“继承关系”和“3条规则”。

继续绕:

例4:

fn main() {
    let mut s = String::new();

    let update_string =  |str| s.push_str(str);
    update_string("hello");

    println!("{:?}",s);
}

报错:

error[E0596]: cannot borrow `update_string` as mutable, as it is not declared as mutable
 --> src/main.rs:5:5
  |
4 |     let update_string =  |str| s.push_str(str);
  |         -------------          - calling `update_string` requires mutable binding due to mutable borrow of `s`
  |         |
  |         help: consider changing this to be mutable: `mut update_string`
5 |     update_string("hello");
  |     ^^^^^^^^^^^^^ cannot borrow as mutable

为什么update_string的类型都FnMut了,还不让动str呢?看看FnMut:

pub trait FnMut<Args: Tuple>: FnOnce<Args> {
    /// Performs the call operation.
    #[unstable(feature = "fn_traits", issue = "29625")]
    extern "rust-call" fn call_mut(&mut self, args: Args) -> Self::Output;
}

这里call_mut要求获得可变的self借用,这里self即update_string,所以,update_string得声明为可变才行。

好了,这就是全部……还有个例子:

let f =  move|| println!("{}", s.len());

猜猜看,上面的f哪几个exec能执行?

答案是都行~因为,其实这个move和前面的讨论并没太大关系,它意思是环境变量我都要move走,之后的代码就不能再用s了。f的类型只取决于闭包里怎么用s,而不取决于怎么捕获它,所以当然还是Fn咯~


网站公告

今日签到

点亮在社区的每一天
去签到