Rust学习笔记Day23 闭包的使用场景,3种常用闭包类型有哪些
昨天我们一起学习了闭包的定义及影响闭包大小的因素。
今天我们接着学习 FnOnce / FnMut / Fn 这三种闭包类型。
FnOnce
代码定义如下:
pub trait FnOnce<Args> {
type Output;
extern "rust-call" fn call_once(self, args: Args) -> Self::Output;
}
Output: 是FnOnce的关联类型,是闭包的返回值类型。call_once: 第一个参数是self,它会转移self的所有权到call_once函数里。Args: 是泛型参数。
FnOnce 被称作 Once :它只能被调用一次。再次调用,编译器就会报变量已经被 move 这样的常见所有权错误了。
比如这个例子:
fn main() {
let name = String::from("name");
// 这个闭包啥也不干,只是把捕获的参数返回去
let c = move |greeting: String| (greeting, name);
let result = c("greeting".to_string());
println!("result: {:?}", result);
// 无法再次调用
// let result = c("hi".to_string());
}
闭包c 只是把参数(greeting)和捕获的(name)返回了。这里会转移闭包内部数据,导致闭包不完整,无法再次使用,所以这里的c是一个FnOnce的闭包。最后一次调用会报错。
作者解释道: 因为把闭包内部数据转移(返回)了,所以只能调用一次,那如果我们不转移呢?
再试一次:
fn main() {
let name = String::from("name");
let c = move |greeting: String| (println!("{} {}",greeting, name));
c("greeting".to_string());
c("hello".to_string());
}
结果如下:
greeting name
hello name
可以看到这回就可以重复执行闭包c了,这时候闭包c就不是FnOnce。
FnMut
看到mut,其实我第一个想到的就是 可变变量:
let mut a = xxx;
我理解这个 FnMut 就是可变闭包,或者说是可以写操作的闭包。
还是先看一下他的代码定义:
pub trait FnMut<Args>: FnOnce<Args> {
extern "rust-call" fn call_mut(
&mut self,
args: Args
) -> Self::Output;
}
FnOnce 是 FnMut 的 super trait。
- FnMut也有FnOnce里的 Output 这个关联类型和call_once这个方法。
- 还有自己的一个call_mut方法。
可以看到 call_mut 的参数是 &mut self,它并不转移self,所以可以多次调用。
如果想要在FnMut闭包内修改捕获的变量,外部变量也要mut 一下。
看个例:
fn main() {
let mut name = String::from("name");
let mut name1 = String::from("hello");
// 捕获 &mut name ,name 需要声明成 mut
let mut c = || {
name.push_str(" 0");
println!("c: {}", name);
};
// 捕获 mut name1,name1 也需要声明成 mut
let mut c1 = move || {
name1.push_str("1");
println!("c1: {}", name1);
};
c();
c1();
call_mut(&mut c);
call_mut(&mut c);
call_mut(&mut c1);
call_mut(&mut c1);
call_once(c);
call_once(c1);
}
// 在作为参数时,FnMut 也要显式地使用 mut,或者 &mut
fn call_mut(c: &mut impl FnMut()) {
c();
}
// 想想看,为啥 call_once 不需要 mut?
fn call_once(c: impl FnOnce()) {
c();
}
运行结果如下:
c: name 0
c1: hello1
c: name 0 0
c: name 0 0 0
c1: hello11
c1: hello111
c: name 0 0 0 0
c1: hello1111
这里在闭包c里捕获了&mut name,因为没有move所有权,所以是借用。在闭包c1里捕获了mut name1,因为move了name1的所有权。
然后演示了call_mut函数的多次调用, 需要使用 &mut self,所以不移动所有权。
Fn
再来看下Fn trait,定义如下:
pub trait Fn<Args>: FnMut<Args> {
extern "rust-call" fn call(&self, args: Args) -> Self::Output;
}
Fn“继承”了 FnMut,或者说 FnMut 是 Fn 的 super trait。
这样一来,** 用FnOnce或FnMut的时候,都可以用Fn的闭包来满足**。
注意:Fn和fn不是一回事儿。fn 是一个 function pointer,不是闭包
使用场景
- thread::spawn。
- Iterator trait里 大部分函数都接收一个闭包。如map。
- 为闭包实现某个trait,让它可以有其他的行为。
小结
Rust闭包效率非常高。
- 闭包里捕获的外部变量,都存储在栈上,没有堆内存的分配。
- 闭包在创建时,会隐式的创建自己的类型,每个闭包都是一个新的类型。
- 不需要额外的函数指针来运行闭包,效率几乎和函数一样。
然后介绍了3种闭包:FnOnce、FnMut 和 Fn。
- FnOnce 只能调用一次;
- FnMut 允许在执行时修改闭包的内部数据,可以执行多次;
- Fn 不允许修改闭包的内部数据,也可以执行多次。
这里有点奇怪的是:FnMut是Fn的super trait,但是FnMut可以修改闭包内部数据,而Fn却不允许修改闭包内部数据?
相关文章
- 前端眼中的Rust
- rust学习笔记:for循环的一些问题
- Rust学习笔记之Rust环境配置和入门指南
- rust 入门笔记: rustlings(推荐一些学习rust语法的一些非常好的小练习)
- rust 入门笔记:使用rust实现双向链表、二叉树
- rust写操作系统 rCore tutorial 学习笔记:实验指导四 进程与线程
- 【Rust日报】2022-12-15 - Rust GUI 库发展现状
- Rust 编程学习笔记Day 5 - Borrow
- Rust编程学习笔记Day7-一个值可以有多个所有者吗?
- Rust变成学习笔记Day9 值的使用及如何销毁?
- Rust学习笔记Day13 怎么用trait实现子类型多态?
- Rust学习笔记 常用trait 类型转换,操作符相关
- Rust学习笔记Day22 何为闭包?闭包的本质是什么?
- Rust学习笔记Day24 常用库及生态领域
- 使用Rust编写一个web服务
- Rust实现高性能Redis服务器集群(rust 实现redis)