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

ops/2024/9/23 4:50:43/

闭包(英语: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咯~


http://www.ppmy.cn/ops/33931.html

相关文章

Vue 3 中的 h() 与 mergeProps() API 详解

前言 在 Vue 3 中&#xff0c;随着 Composition API 的引入&#xff0c;我们有了更多的灵活性和控制权来构建我们的组件。其中&#xff0c;h() 函数和 mergeProps() 是在构建渲染函数或 JSX/TSX 时经常使用的两个工具。下面&#xff0c;我将对这两个 API 进行详细的解释。 h()…

【centos】vmware安装airflow流程

文章目录 1.下载系统https://mirrors.aliyun.com/centos/7/isos/x86_64/ 选择DVD20092.在VMware中&#xff0c;除了修改安装位置&#xff0c;其它选择默认安装centos73.用户名为root,登录4.网络适配器为桥接模式。5.使用命令消除显示器警告。6.安装anaconda6.1下载包&#xff1…

【微服务】分布式事务(通过Seata解决分布式事务问题)

分布式事务 分布式事务Seata微服务集成SeataXA模式XA模式使用 AT模式AT模式实现 分布式事务 在分布式系统中&#xff0c;如果一个业务需要多个服务合作完成&#xff0c;而且每一个服务都有事务&#xff0c;多个事务必须同时成功或失败&#xff0c;这样的事务就是分布式事务&am…

初识webpack项目

新建一个空的工程 -> % mkdir webpack-project 为了方便追踪执行每一个命令&#xff0c;最终产生了哪些变更&#xff0c;将这个空工程初始化成git项目 -> % cd webpack-project/-> % git init Initialized empty Git repository in /Users/lixiang/frontworkspace/…

React-Native制作一个通过照片和视频识别鸟类的安卓app

前言 首先简单介绍一下这个app是干啥的,实际上我的标题已经差不多说完了.....就是用户可以上传带有鸟类的图片或者视频,然后就可以得到相应的识别效果,这个app的需求是来自于我的一位朋友,所以我就做出来了 正文 讲一下基本用到的技术栈吧,前端用的react-native,后端用的是f…

(三)JVM实战——对象的内存布局与执行引擎详解

对象的内存布局 对象的实例化 对象的创建方式 - new的方式 - Class的newInstance():反射的方式 - Construct的newInstance() - clone:实现Cloneable接口,默认浅拷贝 - 使用反序列化&#xff1a;将二进制流转化为内存对象 创建对象的步骤 - 判断对象对应的类是否加载、链接、初…

银行智能化数据安全分类分级实践分享

文章目录 前言一、数据安全智能分类分级平台建设背景二、数据安全分类分级建设思路和实践1、做标签– 数据安全标签体系2、打标签– 鹰眼智能打标平台 3.03、用标签– 全行统一“数据安全打标签结果”服务提供前言 随着国家对数据安全的高度重视,以及相关法律法规的出台,数据…

qwen-vl微调

1.数据格式转换 模版格式&#xff1a; [{"id": "identity_0","conversations": [{"from": "user","value": "你好"},{"from": "assistant","value": "我是Qwen-V…