闭包类型
闭包表达式 生成一个闭包值,具有独特匿名的类型,无法显式书写。 闭包类型类似于包含捕获变量的结构体。例如,以下闭包:
#![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; }; // 下面这一行是错误的: // let y = &x; c(); } let z = &x; }
在这种情况下,无法将 x
借为可变引用,因为 x
不是可变的。
但与此同时,借用 x
为不可变引用将使赋值操作非法,因为 & &mut
引用可能不是唯一的,因此不能安全地用于修改值。
因此,使用了一种独特的不可变借用:它以不可变方式借用 x
,但像可变借用一样,必须是唯一的。
在上面的示例中,取消注释 y
的声明将导致错误,因为它将违反闭包对 x
的借用的唯一性; z
的声明是有效的,因为闭包的生命周期在块的末尾已过期,释放了借用。
调用trait和类型强制转换
闭包类型都实现了 FnOnce
,表示它们可以通过消耗闭包所有权来被调用一次。此外,一些闭包还实现了更具体的调用 trait:
注意:
move
闭包仍然可能实现Fn
或FnMut
,即使它们通过移动捕获变量。 这是因为闭包类型实现的trait是由闭包对捕获值的操作方式决定的,而不是它们如何捕获它们。
非捕获闭包 是不从其环境捕获任何内容的闭包。它们可以强制转换为与匹配签名的函数指针 (例如, 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); }
其他 traits
所有闭包类型都实现了 Sized
。此外,如果允许所存储的捕获类型这样做,则闭包类型会实现以下 traits:
Send
和 Sync
的规则与普通结构体类型相同,而 Clone
和 Copy
的行为则类似于 衍生 。
对于 Clone
,复制捕获变量的顺序未指定。
由于捕获通常是通过引用进行的,因此会出现以下一般规则: