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

news2024/11/16 19:39:03

闭包(英语: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.coloradmin.cn/o/1643190.html

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

相关文章

【设计模式】函数式编程范式工厂模式(Factory Method Pattern)

目录标题 定义函数式接口函数式接口实现类工厂类封装实际应用总结 定义函数式接口 ISellIPad.java /*** 定义一个函数式接口* param <T>*/ FunctionalInterface public interface ISellIPad<T> {T getSellIPadInfo();}函数式接口实现类 HuaWeiSellIPad.java pu…

大气网格化精细化监管监测哪家好?

一、什么是大气网格化精细化监管监测 在当今环境问题日益突出的时代&#xff0c;大气质量监测与监管成为了至关重要的工作。大气网格化精细化监管监测系统的出现&#xff0c;为我们更好地了解和掌握大气环境状况提供了有力手段。然而&#xff0c;面对众多的系统供应商&#xff…

《自动机理论、语言和计算导论》阅读笔记:p352-P401

《自动机理论、语言和计算导论》学习第 12 天&#xff0c;p352-P401总结&#xff0c;总计 50 页。 一、技术总结 1.Turing Machine ™ 2.undecidability ​ a.Ld(the diagonalization language) 3.reduction p392, In general, if we have an algorithm to convert insta…

C#-快速剖析文件和流,并使用(持续更新)

目录 一、概述 二、文件系统 1、检查驱动器信息 2、Path 3、文件和文件夹 三、流 1、FileStream 2、StreamWriter与StreamReader 3、BinaryWriter与BinaryReader 一、概述 文件&#xff0c;具有永久存储及特定顺序的字节组成的一个有序、具有名称的集合&#xff1b; …

Docker私有镜像仓库搭建 带图形化界面的

搭建镜像仓库可以基于Docker官方提供的DockerRegistry来实现。 官网地址&#xff1a;https://hub.docker.com/_/registry 先配置私服的信任地址: # 打开要修改的文件 vi /etc/docker/daemon.json # 添加内容&#xff1a; "insecure-registries":["http://192.…

定时器编程前配置和控制LED隔一秒亮灭

1.配置定时器 0 工作模式16位计时 2.给初值&#xff0c;定一个10ms出来 3.开始计时

pygame学习--精灵组、碰撞检测、精灵更新

pygame学习--精灵组、碰撞检测、精灵更新 一.效果二.代码 通过pygame库,模拟种群的分化 1.X从左往右移动,表示年龄的增加;Y坐标表示阶层 2.随着X坐标不断增大,圆逐渐增大,颜色也加深 3.精灵越多,碰撞后死亡的概率越大,诞生新精灵的概率越小 4.每个精灵都有随机的运动速度及Y坐标…

关于YOLO8学习(五)安卓部署ncnn模型--视频检测

前文 关于YOLO8学习(一)环境搭建,官方检测模型部署到手机 关于YOLO8学习(二)数据集收集,处理 关于YOLO8学习(三)训练自定义的数据集 关于YOLO8学习(四)模型转换为ncnn 简介 本文将会讲解: (1)使用前文生成的ncnn模型,部署到安卓端,并且实现视频中,人脸的检测…

图像压缩问题

图像压缩问题的bilibil讲解 1.问题引入 首先&#xff0c;图像是由像素组合成的&#xff0c;每个像素都有灰度值&#xff0c;灰度值是体现像素的颜色的。灰度值从0~255&#xff0c;灰度值占用的位数就是像素占用的位数。我们要存储一个图像就要存储它的所有像素。现在的问题是我…

【Github】将github仓库作为图床使用

创建github仓库 首先创建一个github仓库专门用于存储图片&#xff0c;具体步骤如下&#xff1a; 1.点击新的仓库按钮 2.初始配置&#xff1a;随便填写一个仓库名&#xff1b;这里的仓库状态一定要是public公开的&#xff0c;不然后面访问不了图片 下载PicGo PicGo官网 在A…

【Linux系统编程】第十三弹---项目自动化构建工具-make/Makefile

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】【C详解】【Linux系统编程】 目录 1、背景 2、编写makefile 2、make原理 3、理解makefile 4、优化makefile 总结 1、背景 ★ 会不会写makefile&#xff0c;从…

杭州等级保护测评—提升企业安全等级

4月初&#xff0c;在和杭州未来科技城正式签署“重点企业落户协议”之后&#xff0c;新一代本地生活服务平台“AC生活”又获得了由中华人民共和国公安部颁发的“信息系统安全等级保护”备案证书。尽管工作人员一再强调“公司还处在研发期”、“产品还不成熟”&#xff0c;但随着…

2.spring security 简单入门

创建springboot 项目&#xff0c;引入spring security坐标 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--spring security坐标--><dependency&g…

【每日力扣】141. 环形链表与142. 环形链表 II

&#x1f525; 个人主页: 黑洞晓威 &#x1f600;你不必等到非常厉害&#xff0c;才敢开始&#xff0c;你需要开始&#xff0c;才会变的非常厉害 141. 环形链表 给你一个链表的头节点 head &#xff0c;判断链表中是否有环。 如果链表中有某个节点&#xff0c;可以通过连续跟…

恶补《操作系统》5_2——王道学习笔记

5.2_1 I-O核心子系统 1、用户层软件 假脱机系统 2、设备独立性软件&#xff08;设备无关性软件&#xff09; IO调度、设备保护、设备分配与回收、缓冲区管理 3、设备驱动程序&#xff08;比如打印机驱动&#xff09; 4、中断处理程序 5、硬件 5.2_2 假脱机技术&#xff…

爬虫工具:避免复制文字收费,VIP。。

step ctrl shift i 打开开发者工具 点击...打开设置勾选禁用JavaScript Principle 事件处理&#xff1a;JavaScript 用于处理用户交互&#xff0c;例如点击、滚动、输入等禁用 JavaScript 后&#xff0c;页面上的所有 JavaScript 代码都将不会执行

基于 Dockerfile 部署 LNMP 架构

目录 前言 1、任务要求 2、Nginx 镜像创建 2.1 建立工作目录并上传相关安装包 2.2 编写 Nginx Dockerfile 脚本 2.3 准备 nginx.conf 配置文件 2.4 生成镜像 2.5 创建 Nginx 镜像的容器 2.6 验证nginx 3、Mysql 镜像创建 3.1 建立工作目录并上传相关安装包 3.2 编写…

FastAPI - Pydantic相关应用

参考链接&#xff1a;Pydantic官方文档 文章目录 定义数据模型创建模型实例数据验证数据转换模型转换模型更新模型配置辅助类Field Pydantic 是一个 Python 库&#xff0c;主要用于数据验证和管理。数据验证是指检查数据是否符合预定的规则和格式&#xff0c;比如检查数据类型是…

c++项目文件夹重命名

文章目录 一、改变项目文件夹名&#xff1f;1. 手动改名2. 自动改名 二、全局替换原文件名的引用1.打开Visual Studio 2022软件 一、改变项目文件夹名&#xff1f; 1. 手动改名 文件名一个一个改挺麻烦的 2. 自动改名 import java.io.File; import java.util.Optional;publi…

leetcode_47.全排列 II

47. 全排列 II 题目描述&#xff1a;给定一个可包含重复数字的序列 nums &#xff0c;按任意顺序 返回所有不重复的全排列。 示例 1&#xff1a; 输入&#xff1a;nums [1,1,2] 输出&#xff1a; [[1,1,2],[1,2,1],[2,1,1]]示例 2&#xff1a; 输入&#xff1a;nums [1,2,3] …