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源码:

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咯~

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

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

相关文章

Linux线程安全,互斥量和条件变量

文章目录 一、 Linux线程互斥1. 进程线程间的互斥相关背景概念&#xff08;1&#xff09; 临界资源和临界区&#xff08;2&#xff09; 互斥和原子性 2. 互斥量mutex3. 互斥量的接口4. 互斥量实现原理探究 二、 可重入VS线程安全1. 概念2. 常见的线程不安全的情况3. 常见的线程…

【superset】基于MySQL的BI数据分析可视化实战案例(已更新)

1.熟悉、梳理、总结下superset可视化分析实战案例知识体系,一直想探索有效可用的可视化分析方案,大多收费或不好用,这里,借此机会总结、更新下。 2.复杂度高,遇到并解决的问题较多,尝试了很多次。 3.欢迎批评指正,跪谢一键三连! 基于MySQL的BI数据分析可视化实战案例文…

Crossplane 实战:构建统一的云原生控制平面

1 什么是 Crossplane Crossplane 是一个开源的 Kubernetes 扩展&#xff0c;其核心目标是将 Kubernetes 转化为一个通用的控制平面&#xff0c;使其能够管理和编排分布于 Kubernetes 集群内外的各种资源。通过扩展 Kubernetes 的功能&#xff0c;Crossplane 对 Kubernetes 集群…

rv1126的rknn1.7.5自有模型训练部署

几乎一年前, 弄过一次rv1126的平台的推理部署, 一年时间过去了, rknn从1.7.1, 进化到了1.7.5,原有的代码不太好用了, 因为最近有个客户要做1126平台的推理, 今天下午就花了几个小时, 从头再捋了一遍. 模型训练 这部分, 跟3588平台差不多, clone下yolov5的仓库, 并check out到…

《QT实用小工具·五十五》带有标签、下划线的Material Design风格输入框

1、概述 源码放在文章末尾 该项目实现了一个带有标签动画、焦点动画、正确提示、错误警告的单行输入框控件。下面是demo演示&#xff1a; 项目部分代码如下所示&#xff1a; #ifndef LABELEDEDIT_H #define LABELEDEDIT_H#include <QObject> #include <QWidget>…

Day15-JavaWeb开发-Maven高级-分模块设计与开发继承与聚合私服

1. Maven高级-分模块设计与开发 2. Maven高级-继承与聚合 2.1 继承关系实现 2.2 版本锁定 2.3 聚合实现 3. Maven高级-私服 3.1 私服-介绍 3.2 私服-资源上传与下载 4. Web开发-完结

Nginx(参数设置总结)

文章目录 Nginx&#xff08;工作机制&参数设置&#xff09;1.Master&Worker工作机制1.示意图2.解释3.Nginx争抢机制4.accept_mutex解决惊群现象5.多进程结构不用多线程结构的好处6.IO多路复用&#xff0c;实现高并发7.优势 2.参数配置1.work_processes1.基本介绍2.work…

15_Scala面向对象编程_访问权限

文章目录 Scala访问权限1.同类中访问2.同包不同类访问3.不同包访问4.子类权限小结 Scala访问权限 知识点概念 private --同类访问private[包名] --包私有&#xff1b; 同类同包下访问protected --同类&#xff0c;或子类 //同包不能访问(default)(public)默认public --公…

【电子通识】为什么IC内部偏置会用到恒流源?

在查看芯片手册时&#xff0c;我们经常会发现芯片框图中出现恒流源。下图所示LM358运算放大器规格书中功能框图的恒流源&#xff1a; 电源芯片SS端内部的恒流源&#xff1a; 其实&#xff0c;IC内部电路的偏置&#xff0c;大多通过恒流源或者恒压源来提供。这与电源波动影响到…

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

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

力扣 647. 回文子串

题目来源&#xff1a;https://leetcode.cn/problems/palindromic-substrings/description/ C题解1&#xff1a;暴力解法。不断地移动窗口&#xff0c;判断是不是回文串。 class Solution { public:int countSubstrings(string s) {int len s.size();int res 0;for(int i 0;…

vscode如何配置python

Visual Studio Code配置Python环境 打开Visual Studio Code&#xff0c;点击主界面最左侧最下的选项&#xff08;extension&#xff09;,在搜索框中输入“python”,点击“Install”进行安装。 如下图所示&#xff1a; 重启或点击“Reload”(重载)后&#xff0c;即可使用&…

Vue工程化开发和脚手架Vue CLI

目录 一、介绍 二、使用步骤 1. 全局安装&#xff08;一次&#xff09; 2.查看Vue版本 3.创建项目架子&#xff08;项目名不能使用中文&#xff09; 4.启动项目 一、介绍 Vue CLI是Vue官方提供的一个全局命令工具。可以帮助我们快速创建一个开发的Vue项目的标准化基础架子…

C++进阶:AVL树

AVL树的概念 二叉搜索树虽可以缩短查找的效率&#xff0c;但 如果数据有序或接近有序二叉搜索树将退化为单支树&#xff0c;查 找元素相当于在顺序表中搜索元素&#xff0c;效率低下 。因此&#xff0c;两位俄罗斯的数学家 G.M. A delson- V elskii 和 E.M. L andis 在 1962 …

C++进阶 | [2] 多态

摘要&#xff1a;多态的概念&#xff0c;多态的条件&#xff0c;虚函数的重写&#xff0c;抽象类&#xff0c;多态的原理&#xff0c;虚函数与虚函数表&#xff0c;与多态有关的问答题 1. Concept 多态的概念&#xff1a;通俗来说&#xff0c;就是多种形态&#xff0c;具体点就…

W801学习笔记十九:古诗学习应用——下

经过前两章的内容&#xff0c;背唐诗的功能基本可以使用了。然而&#xff0c;仅有一种模式未免显得过于单一。因此&#xff0c;在本章中对其进行扩展&#xff0c;增加几种不同的玩法&#xff0c;并且这几种玩法将采用完全不同的判断方式。 玩法一&#xff1a;三分钟限时挑战—…

深入理解网络原理4----TCP核心特性介绍(下)

文章目录 前言一、拥塞控制二、延时应答三、捎带应答四、面向字节流五、异常处理 前言 随着时代的发展&#xff0c;越来越需要计算机之间互相通信&#xff0c;共享软件和数据&#xff0c;即以多个计算机协同⼯作来完成业务&#xff0c;就有了⽹络互连。 一、拥塞控制 虽然TCP有…

使用memcache 和 redis 、 实现session 会话复制和保持

一、NoSQL介绍 NoSQL是对Not Only SQL、非传统关系型数据库的统称 NoSQL一词诞生于1998年&#xff0c;2009年这个词汇再次提出指非关系型、分布式、不提供ACID的数据库设计模式 随着互联网时代的数据爆发时增长、数据库技术发展的日新月异&#xff0c;要适应新的业务需求&am…

[C#] Visual Studio开发工具编译出错匹配目标平台x86或x64架构问题解决

使用Visual Studio开发工具的NuGet管理包插件时&#xff0c;开发者常常会遇到编译错误。尤其是在切换目标平台架构&#xff08;如AnyCPU、x86或x64&#xff09;时&#xff0c;有时会发现切换似乎不起作用&#xff0c;导致编译仍然出错。 文章目录 You need to specify platform…

英语复习之英语形近词总结(二)

接着总结形近词 单词释义例句 impress 英 /ɪmˈpres/ 美 /ɪmˈpres/ vt.盖印&#xff1b;强征&#xff1b;传送&#xff1b;给予某人深刻印象 vi.给人印象。印象&#xff0c;印记&#xff1b;特征&#xff0c;痕迹 1.It didnt impress me as a good place to live. 那地方…