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

news/2024/9/22 15:09:49/

闭包(英语: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/news/1453555.html

相关文章

EPAI手绘建模APP图层、相机、灯光

④ 图层列表 1) 添加图层。 2) 列表显示场景中所有图层。初始时&#xff0c;默认有一个激活的图层。场景中所有模型都会添加到这个图层。 3) 第一次点击图层名称旁边的可见按钮&#xff0c;图层中所有模型都不可见&#xff0c;再次点击&#xff0c;图层中所有模型可见。 4)…

【MySql】FIND_IN_SET 字符串逗号分隔,判断是否包含某字符串

FIND_IN_SET 字符串逗号分隔&#xff0c;判断是否包含某字符串 一、FIND_IN_SET FIND_IN_SET 返回某字符串在一串有逗号组成的字符串集合(SET)中的第几位&#xff0c;不存在时为0 -- 查询结果&#xff1a;4 SELECT FIND_IN_SET( ad, ac,ab,aa,ad )-- 查询结果&#xff1a;0 …

AcWing算法基础课笔记 ------ 第五章 动态规划

文章目录 一. 线性dp1. 数字三角形2. 最长上升子序列3. 最长公共子序列 二. 区间dp1. 石子合并 三. 计数类dp1. 整数划分 四. 状态压缩dp1. 蒙德里安的梦想2. 最短Hamilton路径 五. 树形dp1. 没有上司的舞会 六. 记忆化搜索1. 滑雪 本篇文章是在Acwing种学习动态规划的笔记题解…

公共交通无障碍设施:科技翅膀助力盲人出行新飞跃

在城市的脉络中&#xff0c;公共交通扮演着连接每一个角落的重要角色。然而&#xff0c;对于视力受限的盲人朋友而言&#xff0c;这幅繁忙而复杂的交通网络往往隐藏着诸多不易察觉的障碍。值得庆幸的是&#xff0c;随着公共交通无障碍设施的不断完善&#xff0c;以及高科技辅助…

Python语言例题集(013)

#!/usr/bin/python3 #建立循环链表。 class Node(): def init(self,dataNone): self.datadata self.nextNone n1Node(5) n2Node(15) n3Node(25) n1.nextn2 n2.nextn3 n3.nextn1 ptrn1 counter1 while counter<6: print(ptr.data) ptrptr.next counter1

1.4 Java全栈开发前端+后端(全栈工程师进阶之路)-前置课程java基础语法、java面向对象编程

Java核心语法&#xff1a; 对象&#xff1a;对象是类的一个实例&#xff08;对象不是找个女朋友&#xff09;&#xff0c;有状态和行为。例如&#xff0c;一条狗是一个对象&#xff0c;它的状态有&#xff1a;颜色、名字、品种&#xff1b;行为有&#xff1a;摇尾巴、叫、吃等。…

综合性练习(后端代码练习4)——图书管理系统

目录 一、准备工作 二、约定前后端交互接口 1、需求分析 2、接口定义 &#xff08;1&#xff09;登录接口 &#xff08;2&#xff09;图书列表接口 三、服务器代码 &#xff08;1&#xff09;创建一个UserController类&#xff0c;实现登录验证接口 &#xff…

AI图书推荐:杀手级ChatGPT提示词——利用人工智能实现成功与盈利

《杀手级ChatGPT提示词——利用人工智能实现成功与盈利》&#xff08;Killer ChatGPT Prompts_ Harness the Power of AI for Success and Profit &#xff09;一书是作者Guy Hart-Davis关于ChatGPT的指南&#xff0c;ChatGPT是OpenAI开发的大语言模型。这本书提供了各种职业角…