Rust闭包 - Fn/FnMut/FnOnce traits,捕获和传参

news2024/11/20 0:39:58

Rust闭包: 是一类能够 捕获周围作用域中变量 的 函数


|参数| {函数体}

  • 参数及返回值类型可推导,无需显示标注
  • 类型唯一性,确定后不可更改
  • 函数体为单个表达式时,{}可省略

文章目录

    • 引言
    • 1 分类 Fn / FnMut / FnOnce
    • 2 关键词 move
    • 3 闭包作为参数传递

引言

闭包区别于一般函数最大的特点就是,可以捕获周围作用域(不一定是当前同作用域,上级也可以)中的变量;当然,也可以选择啥都不捕获。

let a = 0;

// 一般函数
// fn f1 () -> i32 {a} // 报错:fn中无法捕获动态环境变量

// 闭包
let f2 = || println("{}", a); // 闭包捕获&a
let f3 = |a: i32|{}; // 闭包啥都没捕获,a只是个普通的形参

这里说的捕获不应该认为是像函数一样简单地传参,可以理解成闭包也是一种语法糖,它背后进行的操作要复杂的多,详细可参考文末相关资料[1]

// 举个栗子,定义了以下闭包并调用
let message = "Hello World!".to_string();
let print_me = || println!("{}", message);

print_me();

其实际进行的操作是这样:

#[derive(Clone, Copy)]
struct __closure_1__<'a> { // note: lifetime parameter
    message: &'a String, // note: &String, 下文会提到所谓的——捕获引用
}

impl<'a> Fn<()> for __closure_1__<'a> {
    // type Output = ();
    
    fn call(&self, (): ()) -> () {
        println!("{}", *self.message)
    }
}

let message = "Hello World!".to_string();
let print_me = __closure_1__ { message: &message };


Fn::call(&print_me, ());

1 分类 Fn / FnMut / FnOnce

根据捕获变量进行的操作,Rust里的闭包实现的traits共三种
注意!这里的因果关系,是捕获变量的操作 决定 闭包实现的形式

  • Fn : 可在不改变状态的情况下重复调用; 捕获变量的不可变引用(shared reference)或啥都不捕获
  • FnMut: 可改变状态,可重复调用; 捕获变量的可变引用(mutable reference
  • FnOnce: 只能调用一次,存在捕获的变量所有权转移被消耗
// 闭包impl trait编译器会自动根据捕获操作推导,注释方便阅读
let a = 0;
// impl Fn()
let f1 = || println("{}", a); // 捕获&a
f1();
f1();

let mut b = 0;
// impl FnMut()
let mut f2 = || b+=1; // 捕获&mut b; 可能会有疑问为什么不需要解引用*b+=1, 参考相关资料[1]
f2();
f2();

let c = "".to_string();
// impl FnOnce()
let f3 = || std::mem::drop(c);
f3();
//f3(); // 报错,f3只能调用一次,c所有权已经发生了转移并且消费了它

2 关键词 move

move将引用或可变引用捕获的任何变量转换为按值捕获的变量
注意!闭包实现的traits是由对值进行的操作确定,而不是捕获值的方式;这意味即使闭包中捕获的是值,发生了所有权转移,它也可能是FnFnMut [2]

(1) 实现Copy trait的对象,move时发生值拷贝

let a = 0;
// impl Fn()
let f1 = move || println("{}", a); // 将捕获的不可变引用转换为值拷贝传递给闭包

let mut b = 0;
// impl FnMut()
let mut f2 = move || b += 1;
f2();
f2();
println("{}", b); // 因为闭包里是值拷贝,所以还是0

(2)未实现Copy trait的对象,move时发生所有权转移

let a = "".to_string();
// impl Fn()
let f1 = move || println!("{}", a); // 环境中变量a对应值的所有权转移给了闭包a
// 因为并未产生消耗,所以类型推导仍然是Fn,f1可以反复调用
f1();
f1();
// println("{}", a); // 报错,使用了值已发生move的a

let mut b = "".to_string();
// impl FnMut()
let mut f2 = move || {
	b += "x";
	println("{}", b);
};
f2(); // x
f2(); // xx
// println("{}", b); // 报错,使用了值已发生move的b

let c = "".to_string();
// impl FnOnce()
let f3 = move || {
	println("{}", c);
	std::mem::drop(c); // 这边有没有move其实都一样,闭包drop未实现Copy的值,默认捕获的就是转移了所有权的环境变量
};
f3(); 

(3)一些需要注意的点

  • 闭包中,若环境变量直接作为返回值,会以值的形式返回 [1]
// 实现了Copy类型的数据
let mut a = 0;
// impl FnMut() -> i32
let mut f1  = || {
	a += 1// 捕获a引用
	a // 没有";" 闭包类型推导的返回值是i32
}; 
f1();
f1();
println!("{}", a); // 2

// 未实现Copy类型的数据
let mut b = "".to_string();
// impl FnOnce() -> String
let mut f2 = || {
	b += "x";  // 捕获所有权转移的b
	b // 没有";" 返回所有权转移的b; 因为所有权发生转移,并作为返回值传递(消费),所以无法反复调用,故类型推导是FnOnce
}
f2();
  • 有些场景会对未实现Copy的变量触发隐式的move
    (没有找到相关的资料,暂且只能靠记忆)
// std::mem::drop 参考之前的例子

// path statement
let a = "".to_string();
// impl FnOnce() 
let f1 = || {a;}; 

// operation statement
let b = "".to_string();
// impl FnOnce()
let f2 = || {b+"x";};

3 闭包作为参数传递

Fn 继承自 FnMut 继承自 FnOnce
在这里插入图片描述
根据继承关系可以得到结论:

  • 当形参类型为Fn时,只能传递Fn
  • 当形参类型为FnMut时,可以传递 Fn, FnMut
  • 当形参类型为FnOnce,三种皆可

定义:

fn is_fn<F>(_: F) where F: Fn() -> () {}

fn is_fn_mut<F>(_: F) where F: FnMut() -> () {}

fn is_fn_once<F>(_: F) where F: FnOnce() -> () {}

调用:

// impl Fn()
let f1 = || {};

let mut count = 0;
// impl FnMut()
let mut f2 = || count += 1;

let s = "".to_string();
// impl FnOnce()
let f3 = || std::mem::drop(s);

is_fn(f1);

is_fn_mut(f1);
is_fn_mut(&mut f2);

is_fn_once(f1);
is_fn_once(&mut f2);
is_fn_once(f3);

注意!!!这里不能调用 is_fn_mut(f2)
原因是闭包本身作为Fn*类型的数据,也是要考虑其本身Copy trait的实现:参考[3]

  • 若未发生捕获,或捕获的是值拷贝,或只进行了不可变的引用(shared reference),那么闭包本身也实现了Copy trait;
// impl Fn(), 未捕获
let fn_f1 = || {}; 
is_fn(fn_f1);
is_fn(fn_f1);

// impl FnMut(), 捕获值拷贝
let mut a = 0;
let mut fnmut_f2 = move || count1 += 1; 
is_fn_mut(fnmut_f2);
is_fn_mut(fnmut_f2);

// impl Fn(), 捕获不可变引用
let b = 0;
let fn_f3 = || println("", b);
is_fn(fn_f3);
is_fn(fn_f3);
  • 若捕获的是可变引用(mutable reference),那么闭包本身则未实现Copy trait,需要注意所有权转移的可能
fn is_fn_mut<F>(_: F) where F: FnMut() -> () {}

let mut count = 0;
// impl FnMut()
let mut f2 = || count += 1;
is_fn_mut(f2); // 仅调用一次没问题,但是此时f2所有权已经发生了move
//is_fn_mut(f2); // 报错,使用了发生move的f2

想要多次调用的话,需传递&mut f2&mut F也是实现了FnMut的,所以这里传递引用没有问题,参考[4]

is_fn_mut(&mut f2);
is_fn_mut(&mut f2);


相关资料:
[1] https://users.rust-lang.org/t/closure-capture-by-borrowing-is-not-a-regular-reference/55945/8
[2] https://rustwiki.org/zh-CN/std/keyword.move.html
[3] Additional implementors 其他实现者
英 https://doc.rust-lang.org/core/marker/trait.Copy.html
中 https://rustwiki.org/zh-CN/std/marker/trait.Copy.html
[4] https://rustwiki.org/zh-CN/std/ops/trait.FnMut.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1094920.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MySQL8安装步骤

本次安装所需版本为MySQL8.0.24,客户端工具选用的是Navicat11. 大家可以进入官网下载其它版本&#xff0c; 1.双击安装文件 2.等待一段时间进入以下界面 3.选择Custom,点击Next 4.进入以下界面&#xff0c;需要C2019的环境&#xff0c;选中此行然后Execute执行。 5.点击Exe…

论文解析——一种多核处理器直连接口QoS的设计与验证

作者 罗莉&#xff0c;周宏伟&#xff0c;周理&#xff0c;潘国腾&#xff0c;周海亮&#xff08; 国防科技大学&#xff09; 刘彬 &#xff08; 武警贵州省总队&#xff09; 摘要 多核处理器直接互连构建多路并行系统&#xff0c;一直是提高高性能计算机并行性的主要方式。…

【Hello Algorithm】暴力递归到动态规划(三)

暴力递归到动态规划&#xff08;三&#xff09; 最长公共子序列递归版本动态规划 最长回文串子序列方法一方法二递归版本动态规划 象棋问题递归版本动态规划 咖啡机问题递归版本动态规划 最长公共子序列 这是leetcode上的一道原题 题目连接如下 最长公共子序列 题目描述如下…

三种对象注入的区别以及@Autowired和@Resource的区别

文章目录 1. 对象注入1.2 属性注入1.2.1 属性注入的优缺点 1.3 Setter方法注入1.3.1 Setter注入的优缺点 1.4 构造方法注入&#xff08;官方推荐&#xff09;1.4.1 构造方法的优缺点 1.5 Resource与Autowired区别 1. 对象注入 获取Bean对象也称为对象注入/对象装配&#xff0c…

Linux:Termius连接本地虚拟机与虚拟机快照

Termius连接本地虚拟机与虚拟机快照 1. Termius连接本地虚拟机2. 虚拟机快照与还原2.1 设置快照以及恢复 附录 1. Termius连接本地虚拟机 ifconfig -a 查看配置 连接成功 2. 虚拟机快照与还原 在学习阶段我们无法避免的可能损坏Linux操作系统。 如果损坏的话&#xff0c;重新…

FPGA复习(功耗)

减小功耗 就得减小电流 电流和CF有关&#xff08; C: 电容&#xff08;被门数目和布线长度影响&#xff09; F:时钟频率&#xff09; 方法大纲 减小功耗&#xff1a;1 时钟控制 2输入控制 3减小供电电压 4双沿触发器 5修改终端 同步数字电路降低动态功耗&#xff1a;动态禁止…

零食百货经营商城小程序的作用是什么

零食可以只指某款单品&#xff0c;也可以是一堆各品牌食品&#xff0c;其行业涵盖人群广泛&#xff0c;主要以零售和批发为主&#xff0c;不受限制&#xff0c;各地从业商家也非常多&#xff0c;但随着线上电商崛起&#xff0c;零食经营痛点也逐渐凸显。 通过【雨科】平台搭建零…

Elasticsearch实现检索词自动补全(检索词补全,自动纠错,拼音补全,繁简转换) 包含demo

Elasticsearch实现检索词自动补全 自动补全定义映射字段建立索引测试自动补全 自动纠错查询语句查询结果 拼音补全与繁简转换安装 elasticsearch-analysis-pinyin 插件定义索引与映射建立拼音自动补全索引测试拼音自动补全测试繁简转换自动补全 代码实现demo结构demo获取 自动补…

C# 图解教程 第5版 —— 第5章 类的基本概念

文章目录 5.1 类的概述5.2 程序和类&#xff1a;一个简单的示例&#xff08;*&#xff09;5.3 声明类&#xff08;*&#xff09;5.4 类成员&#xff08;*&#xff09;5.4.1 字段&#xff08;*&#xff09;5.4.2 方法 5.5 创建变量和类的实例&#xff08;*&#xff09;5.6 为数据…

解析找不到msvcp140.dll的5个解决方法,快速修复dll丢失问题

​在使用计算机过程中&#xff0c;我们也会遇到各种各样的问题。其中&#xff0c;找不到msvcp140.dll修复方法是一个非常普遍的问题。msvcp140.dll是一个动态链接库文件&#xff0c;它是Microsoft Visual C 2015 Redistributable的一部分。这个文件包含了许多用于运行C程序的函…

计算机网络基础(三):IPv4编址方式、子网划分、IPv4通信的建立与验证及ICMP协议

**IPv4地址是一个32位长的二进制数。**而这个32位二进制数又通常会表示为4个用点隔开的十进制数。那么&#xff0c;这个32位二进制数要如何通过4个十进制数表示出来呢&#xff1f; 我们在配置IPv4地址时&#xff0c;同时配置的“掩码”又有何用途&#xff1f; 1.IPv4编址方式…

MySQL 约束,视图,索引及常见函数

​​​​​​ ​​​​​​​ 2-MySQL 约束,视图,索引及常见函 1 SQL约束 SQL 约束用于规定表中的数据规则。实际上就是表中数据的限制条件。是为了保证数据的完整性而实现的一套机制。 MySQL的约束种类如下&#xff1a; 非空约束&#xff1a;NOT NULL NOT NULL约束强制…

汉堡炸鸡快餐店商城小程序的作用是什么

汉堡炸鸡等快餐店是不少年轻人常去的餐饮店&#xff0c;市场中除了头部品牌外&#xff0c;还有不少中小品牌&#xff0c;消费者选择度高&#xff0c;然而在实际经营中&#xff0c;面对线下流量匮乏、互联网电商发展&#xff0c;快餐店经营痛点不少。 对炸鸡汉堡店来说&#xf…

全球产业链:脑机接口产业链

本心、输入输出、结果 文章目录 全球产业链:脑机接口产业链前言马斯克旗下的脑机接口公司`Neuralink`宣布概念:什么是脑机接口脑机接口技术有哪几种路线脑机接口未来在各行业的应用脑机接口产业链上游脑机接口芯片脑电采集设备系统软件手术耗材脑机接口产业链中游脑机接口产业…

扩散模型的系统性学习(一):DDPM的学习

文章目录 一、学习的资料1.1 对于扩散模型的发展过程的综述1.2对论文中涉及的公式以及公式对应的代码的解读1.3github中对于各模型实现的代码1.4相关基础知识的学习 二、DDPM的学习2.1 DDPM总体知识的梳理2.2相关代码的解读2.2.1unet 代码块2.2.2高斯扩散代码块2.2.3 实验流程代…

【环境搭建】linux docker-compose安装seata1.6.1,使用nacos注册、db模式

新建目录&#xff0c;挂载用 mkdir -p /data/docker/seata/resources mkdir -p /data/docker/seata/logs 给权限 chmod -R 777 /data/docker/seata 先在/data/docker/seata目录编写一个使用file启动的docker-compose.yml文件&#xff08;seata包目录的script文件夹有&#…

常见的网络攻击手段

网络攻击对个人、组织和整个社会都带来了严重的威胁&#xff0c;因此必须采取有效的安全措施来保护网络系统和用户的信息安全。网站是攻击者经常瞄准的目标&#xff0c;以下是一些常见的攻击方式&#xff1a; 1. DDoS攻击&#xff08;分布式拒绝服务攻击&#xff09;&#xff1…

Unity引擎:收费模式和服务升级,为游戏开发带来更多可能性

Unity 引擎的收费模式和配套服务升级已经引起了广泛的关注和讨论。自 2024 年 1 月 1 日起&#xff0c;Unity 将根据游戏的安装量对开发者进行收费。这将会影响到很多游戏开发者和玩家。本文将探讨 Unity 引擎的收费模式和配套服务更新&#xff0c;以及对游戏开发者和玩家的影响…

数据结构 - 6(优先级队列(堆)13000字详解)

一&#xff1a;堆 1.1 堆的基本概念 堆分为两种&#xff1a;大堆和小堆。它们之间的区别在于元素在堆中的排列顺序和访问方式。 大堆&#xff08;Max Heap&#xff09;&#xff1a; 在大堆中&#xff0c;父节点的值比它的子节点的值要大。也就是说&#xff0c;堆的根节点是堆…

四川竹哲电子商务有限公司怎么样?可靠吗?

随着抖音等短视频平台的火热发展&#xff0c;越来越多的人开始关注如何在抖音上获得更多的关注和粉丝。而四川竹哲电子商务有限公司作为一家专业的抖音培训服务公司&#xff0c;正是帮助这些人实现梦想的地方。 首先&#xff0c;四川竹哲电子商务有限公司的抖音培训服务有着丰…