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

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

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

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

以下是三个 trait 的区别

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

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

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

先看function.rs源码:

rust 复制代码
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没啥问题。

再看几个例子

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

rust 复制代码
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了环境变量的闭包:

rust 复制代码
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):

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

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

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

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

继续绕:

例4:

rust 复制代码
fn main() {
    let mut s = String::new();

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

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

报错:

rust 复制代码
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:

rust 复制代码
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咯~

相关推荐
许野平18 分钟前
Rust: 利用 chrono 库实现日期和字符串互相转换
开发语言·后端·rust·字符串·转换·日期·chrono
也无晴也无风雨21 分钟前
在JS中, 0 == [0] 吗
开发语言·javascript
狂奔solar29 分钟前
yelp数据集上识别潜在的热门商家
开发语言·python
blammmp1 小时前
Java:数据结构-枚举
java·开发语言·数据结构
何曾参静谧1 小时前
「C/C++」C/C++ 指针篇 之 指针运算
c语言·开发语言·c++
暗黑起源喵1 小时前
设计模式-工厂设计模式
java·开发语言·设计模式
WaaTong1 小时前
Java反射
java·开发语言·反射
Troc_wangpeng2 小时前
R language 关于二维平面直角坐标系的制作
开发语言·机器学习
努力的家伙是不讨厌的2 小时前
解析json导出csv或者直接入库
开发语言·python·json
Envyᥫᩣ2 小时前
C#语言:从入门到精通
开发语言·c#