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

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 parametermessage: &'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的alet mut b = "".to_string();
// impl FnMut()
let mut f2 = move || {b += "x";println("{}", b);
};
f2(); // x
f2(); // xx
// println("{}", b); // 报错,使用了值已发生move的blet 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";  // 捕获所有权转移的bb // 没有";" 返回所有权转移的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.mzph.cn/news/105915.shtml

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

相关文章

vscode利用lauch.json和docker中的delve调试本地crdb

---- vscode利用delve调试crdb 创建了一个delve容器用于debug crdbdelve&#xff1a; Delve是一个用于Go编程语言的调试器。它提供了一组命令和功能&#xff0c;可以帮助开发人员在调试过程中检查变量、设置断点、单步执行代码等操作。Delve可以与Go程序一起使用&#xff0c;…

【LLM】浅谈 StreamingLLM中的attention sink和sink token

前言 Softmax函数 SoftMax ( x ) i e x i e x 1 ∑ j 2 N e x j , x 1 ≫ x j , j ∈ 2 , … , N \text{SoftMax}(x)_i \frac{e^{x_i}}{e^{x_1} \sum_{j2}^{N} e^{x_j}}, \quad x_1 \gg x_j, j \in 2, \dots, N SoftMax(x)i​ex1​∑j2N​exj​exi​​,x1​≫xj​,j∈2,……

【爬虫实战】python微博热搜榜Top50

一.最终效果 二.项目代码 2.1 新建项目 本文使用scrapy分布式、多线程爬虫框架编写的高性能爬虫&#xff0c;因此新建、运行scrapy项目3步骤&#xff1a; 1.新建项目: scrapy startproject weibo_hot 2.新建 spider: scrapy genspider hot_search "weibo.com" 3…

揭秘元宇宙背后最炫科技风:数字经济时代,元宇宙发展解决方案及核心技术

文章目录 前言一、关于“元宇宙”业界趋势1.1、元宇宙的概念与发展历程1.2、行业应用体验向虚实融合和实时互动演进1.3、数字内容成为各行业 3D 数字世界入口 二、对于元宇宙发展的解决方案和实践2.1、MetaStudio 构建场景化全栈能力2.2、企业 3D 空间&#xff0c;围绕 4 类场景…

最新ai创作系统CHATGPT系统源码+支持GPT4.0+支持ai绘画(Midjourney)

一、AI创作系统 SparkAi创作系统是基于OpenAI很火的ChatGPT进行开发的Ai智能问答系统AI绘画系统&#xff0c;支持OpenAI GPT全模型国内AI全模型。本期针对源码系统整体测试下来非常完美&#xff0c;可以说SparkAi是目前国内一款的ChatGPT对接OpenAI软件系统。那么如何搭建部署…

【Python从入门到进阶】39、使用Selenium自动验证滑块登录

接上篇《38、selenium关于Chrome handless的基本使用》 上一篇我们介绍了selenium中有关Chrome的无头版浏览器Chrome Handless的使用。本篇我们使用selenium做一些常见的复杂验证功能&#xff0c;首先我们来讲解如何进行滑块自动验证的操作。 一、测试用例介绍 我们要通过sel…

MyBatis底层源码分析

&#x1f384;欢迎来到边境矢梦的csdn博文&#x1f384; &#x1f384;本文主要梳理MyBatis底层源码分析 &#x1f384; &#x1f308;我是边境矢梦&#xff0c;一个正在为秋招和算法竞赛做准备的学生&#x1f308; &#x1f386;喜欢的朋友可以关注一下&#x1faf0;&#x1f…

oracle创建数据库,导入dmp操作全家桶

背景&#xff1a;小明在一家IT公司就职&#xff0c;通过查看项目&#xff0c;公司使用的是oracle&#xff0c;几天后&#xff0c;经理要求他从服务器导入数据库到公司服务器&#xff0c;聪明的小明就开始干了起来&#xff0c;整理如下教程。 说明&#xff1a;此次演示环境oracl…

C语言 —— 指针

目录 1. 指针是什么&#xff1f; 2. 指针和指针类型的关系 2.1 指针的解引用 2.2 指针-整数 3. 野指针 3.1 野指针成因 1. 指针未初始化 2. 指针越界访问 3. 指针指向的空间释放 3.2 如何规避野指针 4. 指针运算 4.1 指针-整数 4.2 指针-指针 指针-指针的使用 4.3 指针的关系运…

代码更换了目录,没有任何变更,但Idea编辑器却提示所有代码都变更了?

开发环境&#xff1a; springboot 2.4.3idea 2020 问题描述&#xff1a; 1、代码copy到U盘了&#xff0c;今天用idea打开U盘代码&#xff0c;却提示所有代码都被修改了 2、diff 文件看了&#xff0c;其实并没有任何修改&#xff0c;因为就算不小心误改了&#xff0c;也不能全…

Stable Diffusion绘画,卡通,教室

1 girl, parted lips, blush, makeup, light smile, school uniform, classroom, light rays, glow, thighs, collarbone, narrow waist, (masterpiece), wallpaper 1个女孩&#xff0c;双唇&#xff0c;腮红&#xff0c;化妆&#xff0c;浅笑&#xff0c;校服&#xff0c;教室…

单链表经典OJ题

目录 ​编辑 题目&#xff1a; 一、移除链表元素&#xff1a; 本质&#xff1a; 解题思路&#xff1a; 本题分为两种解法&#xff1a; 我们使用解法二&#xff1a; 注意事项&#xff1a; 完整代码&#xff1a; 题目&#xff1a; 一、移除链表元素&#xff1a; 本质&…

C++11智能指针

目录 一、什么是智能指针&#xff1f;二、为什么需要智能指针&#xff1f;三、内存泄漏3.1 什么是内存泄漏&#xff1f;内存泄漏的危害是什么&#xff1f;3.2 内存泄漏的分类3.3 如何检测内存泄漏&#xff1f;3.4 如何避免内存泄漏&#xff1f; 四、智能指针的使用及原理4.1 RA…

Kotlin vs Java:为什么Springboot官方教程选择了Kotlin?

导语 作为Java开发者的你&#xff0c;是否在为寻找Java的替代品而烦恼&#xff1f;担心受知识产权问题困扰&#xff1f;别担心&#xff0c;Kotlin来了&#xff01;它是你的救星&#xff0c;也是Springboot官网教程的选择。想知道为什么吗&#xff1f;那就往下翻吧&#xff01;…

NeurIPS 2023 | MQ-Det: 首个支持多模态查询的开放世界目标检测大模型

目前的开放世界目标检测模型大多遵循文本查询的模式&#xff0c;即利用类别文本描述在目标图像中查询潜在目标。然而&#xff0c;这种方式往往会面临“广而不精”的问题。一图胜千言&#xff0c;为此&#xff0c;作者提出了基于多模态查询的目标检测&#xff08;MQ-Det&#xf…

傅里叶变换和其图像处理中的应用

以下部分文字资料整合于网络&#xff0c;本文仅供自己学习用&#xff01; 一、为什么要在频域进行图像处理&#xff1f; 一些在空间域表述困难的增强任务&#xff0c;在频率域中变得非常普通 滤波在频率域更为直观&#xff0c;你想想嘛&#xff0c;所谓滤波&#xff0c;就是…

KOSMOS-2.5:密集文本的多模态读写模型

Overview 总览摘要1 引言2 KOSMOS-2.52.1 模型结构2.1 图像和文本表征2.3 预训练数据2.4 数据处理2.5 过滤与质量控制 3 实验3.1 评估3.2 实现细节3.3 结果3.4 讨论 4 相关工作4.1 多模态大语言模型4.2 图文理解 5 总结与展望 总览 题目: KOSMOS-2.5: A Multimodal Literate M…

通过jsoup抓取谷歌商店评分

文章目录 背景实现是否下架预警评分 总的工具类,测试 背景 在谷歌上面发布包,有时候要看看评分,有时候会因为总总原因被下架,希望后台能够对评分进行预警,和下架预警 实现 测试地址: https://play.google.com/store/apps/details?idcom.tencent.mm 通过jsoup解析页面,然后获…

Python学习----Day07

函数 函数是组织好的&#xff0c;可重复使用的&#xff0c;用来实现单一&#xff0c;或相关联功能的代码段。函数能提高应用的模块性&#xff0c;和代码的重复利用率。你已经知道Python提供了许多内建函数&#xff0c;比如print()。但你也可以自己创建函数&#xff0c;这被叫做…

苍穹外卖(五) 微信小程序

项目应用: 使用微信小程序完成客户端开发并基于微信登录实现小程序的登录功能如果是新用户需要自动完成注册 微信小程序开发 介绍 小程序是一种新的开放能力&#xff0c;开发者可以快速地开发一个小程序。可以在微信内被便捷地获取和传播&#xff0c;同时具有出色的使用体验…