闭包类型
一个 闭包表达式 会生成一个具有唯一、匿名类型的闭包值,该类型无法写出。闭包类型大致相当于一个包含捕获变量的结构体。例如,以下闭包
#![allow(unused)] fn main() { fn f<F : FnOnce() -> String> (g: F) { println!("{}", g()); } let mut s = String::from("foo"); let t = String::from("bar"); f(|| { s += &t; s }); // Prints "foobar". }
生成的闭包类型大致如下
struct Closure<'a> {
s : String,
t : &'a String,
}
impl<'a> FnOnce<()> for Closure<'a> {
type Output = String;
fn call_once(self) -> String {
self.s += &*self.t;
self.s
}
}
因此,对 f
的调用就像它是
f(Closure{s: s, t: &t});
捕获模式
编译器倾向于通过不可变借用捕获闭包变量,然后是唯一不可变借用(见下文),然后是可变借用,最后是移动。它将选择与闭包体内如何使用捕获变量兼容的第一个选项。编译器不考虑周围的代码,例如涉及变量的生命周期或闭包本身的生命周期。
如果使用 move
关键字,则所有捕获都通过移动进行,或者对于 Copy
类型,则通过复制进行,而不管借用是否有效。move
关键字通常用于允许闭包的生命周期超过捕获的值,例如,如果闭包正在被返回或用于生成新线程。
复合类型(如结构体、元组和枚举)始终是整体捕获的,而不是按单个字段捕获的。可能需要借用到局部变量才能捕获单个字段
#![allow(unused)] fn main() { use std::collections::HashSet; struct SetVec { set: HashSet<u32>, vec: Vec<u32> } impl SetVec { fn populate(&mut self) { let vec = &mut self.vec; self.set.iter().for_each(|&n| { vec.push(n); }) } } }
相反,如果闭包直接使用 self.vec
,则它将尝试通过可变引用捕获 self
。但是,由于 self.set
已经被借用进行迭代,因此代码将无法编译。
捕获中的唯一不可变借用
捕获可以通过一种称为*唯一不可变借用*的特殊借用发生,这种借用不能在语言中的任何其他地方使用,也不能显式写出。它发生在修改可变引用的引用时,如下例所示
#![allow(unused)] fn main() { let mut b = false; let x = &mut b; { let mut c = || { *x = true; }; // The following line is an error: // let y = &x; c(); } let z = &x; }
在这种情况下,无法可变地借用 x
,因为 x
不是 mut
。但与此同时,不可变地借用 x
将使赋值非法,因为 & &mut
引用可能不是唯一的,因此不能安全地用于修改值。因此,使用唯一不可变借用:它不可变地借用 x
,但像可变借用一样,它必须是唯一的。在上面的例子中,取消注释 y
的声明将产生一个错误,因为它会违反闭包对 x
的借用的唯一性;z
的声明是有效的,因为闭包的生命周期在块的末尾已经结束,释放了借用。
调用特征和强制转换
所有闭包类型都实现了 FnOnce
,这表明可以通过消耗闭包的所有权来调用它们一次。此外,一些闭包实现了更具体的调用特征
注意:
move
闭包可能仍然实现了Fn
或FnMut
,即使它们通过移动捕获变量。这是因为闭包类型实现的特征是由闭包对捕获值的处理方式决定的,而不是由它如何捕获它们决定的。
非捕获闭包是不从其环境中捕获任何内容的闭包。它们可以被强制转换为具有匹配签名的函数指针(例如,fn()
)。
#![allow(unused)] fn main() { let add = |x, y| x + y; let mut x = add(5,7); type Binop = fn(i32, i32) -> i32; let bo: Binop = add; x = bo(5,7); }
其他特征
所有闭包类型都实现了 Sized
。此外,如果它存储的捕获类型允许,则闭包类型还实现了以下特征
Send
和 Sync
的规则与普通结构体类型相匹配,而 Clone
和 Copy
的行为就像是被 派生 的一样。对于 Clone
,捕获变量的克隆顺序未指定。
因为捕获通常是通过引用进行的,所以会出现以下一般规则