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

2024-05-06 06:20

本文主要是介绍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咯~

这篇关于Rust里的Fn/FnMut/FnOnce和闭包匿名函数关系的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/963624

相关文章

MySQL常用字符串函数示例和场景介绍

《MySQL常用字符串函数示例和场景介绍》MySQL提供了丰富的字符串函数帮助我们高效地对字符串进行处理、转换和分析,本文我将全面且深入地介绍MySQL常用的字符串函数,并结合具体示例和场景,帮你熟练... 目录一、字符串函数概述1.1 字符串函数的作用1.2 字符串函数分类二、字符串长度与统计函数2.1

python使用try函数详解

《python使用try函数详解》Pythontry语句用于异常处理,支持捕获特定/多种异常、else/final子句确保资源释放,结合with语句自动清理,可自定义异常及嵌套结构,灵活应对错误场景... 目录try 函数的基本语法捕获特定异常捕获多个异常使用 else 子句使用 finally 子句捕获所

postgresql使用UUID函数的方法

《postgresql使用UUID函数的方法》本文给大家介绍postgresql使用UUID函数的方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录PostgreSQL有两种生成uuid的方法。可以先通过sql查看是否已安装扩展函数,和可以安装的扩展函数

MySQL字符串常用函数详解

《MySQL字符串常用函数详解》本文给大家介绍MySQL字符串常用函数,本文结合实例代码给大家介绍的非常详细,对大家学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录mysql字符串常用函数一、获取二、大小写转换三、拼接四、截取五、比较、反转、替换六、去空白、填充MySQL字符串常用函数一、

C++中assign函数的使用

《C++中assign函数的使用》在C++标准模板库中,std::list等容器都提供了assign成员函数,它比操作符更灵活,支持多种初始化方式,下面就来介绍一下assign的用法,具有一定的参考价... 目录​1.assign的基本功能​​语法​2. 具体用法示例​​​(1) 填充n个相同值​​(2)

MySql基本查询之表的增删查改+聚合函数案例详解

《MySql基本查询之表的增删查改+聚合函数案例详解》本文详解SQL的CURD操作INSERT用于数据插入(单行/多行及冲突处理),SELECT实现数据检索(列选择、条件过滤、排序分页),UPDATE... 目录一、Create1.1 单行数据 + 全列插入1.2 多行数据 + 指定列插入1.3 插入否则更

PostgreSQL中rank()窗口函数实用指南与示例

《PostgreSQL中rank()窗口函数实用指南与示例》在数据分析和数据库管理中,经常需要对数据进行排名操作,PostgreSQL提供了强大的窗口函数rank(),可以方便地对结果集中的行进行排名... 目录一、rank()函数简介二、基础示例:部门内员工薪资排名示例数据排名查询三、高级应用示例1. 每

全面掌握 SQL 中的 DATEDIFF函数及用法最佳实践

《全面掌握SQL中的DATEDIFF函数及用法最佳实践》本文解析DATEDIFF在不同数据库中的差异,强调其边界计算原理,探讨应用场景及陷阱,推荐根据需求选择TIMESTAMPDIFF或inte... 目录1. 核心概念:DATEDIFF 究竟在计算什么?2. 主流数据库中的 DATEDIFF 实现2.1

MySQL中的LENGTH()函数用法详解与实例分析

《MySQL中的LENGTH()函数用法详解与实例分析》MySQLLENGTH()函数用于计算字符串的字节长度,区别于CHAR_LENGTH()的字符长度,适用于多字节字符集(如UTF-8)的数据验证... 目录1. LENGTH()函数的基本语法2. LENGTH()函数的返回值2.1 示例1:计算字符串

MySQL 中的 CAST 函数详解及常见用法

《MySQL中的CAST函数详解及常见用法》CAST函数是MySQL中用于数据类型转换的重要函数,它允许你将一个值从一种数据类型转换为另一种数据类型,本文给大家介绍MySQL中的CAST... 目录mysql 中的 CAST 函数详解一、基本语法二、支持的数据类型三、常见用法示例1. 字符串转数字2. 数字