昨天我们一起学习了闭包的定义及影响闭包大小的因素。
今天我们接着学习 FnOnce / FnMut / Fn 这三种闭包类型。
代码定义如下:
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。
看到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。
可以看到 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 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,不是闭包
Rust闭包效率非常高。
然后介绍了3种闭包:FnOnce、FnMut 和 Fn。
这里有点奇怪的是:FnMut是Fn的super trait,但是FnMut可以修改闭包内部数据,而Fn却不允许修改闭包内部数据?