闭包类型

一个 闭包表达式 会生成一个具有唯一、匿名类型的闭包值,该类型无法写出。闭包类型大致相当于一个包含捕获变量的结构体。例如,以下闭包

#![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,这表明可以通过消耗闭包的所有权来调用它们一次。此外,一些闭包实现了更具体的调用特征

  • 不从任何捕获变量中移出的闭包实现了 FnMut,这表明可以通过可变引用调用它。

  • 不改变或移出任何捕获变量的闭包实现了 Fn,这表明可以通过共享引用调用它。

注意:move 闭包可能仍然实现了 FnFnMut,即使它们通过移动捕获变量。这是因为闭包类型实现的特征是由闭包对捕获值的处理方式决定的,而不是由它如何捕获它们决定的。

非捕获闭包是不从其环境中捕获任何内容的闭包。它们可以被强制转换为具有匹配签名的函数指针(例如,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。此外,如果它存储的捕获类型允许,则闭包类型还实现了以下特征

SendSync 的规则与普通结构体类型相匹配,而 CloneCopy 的行为就像是被 派生 的一样。对于 Clone,捕获变量的克隆顺序未指定。

因为捕获通常是通过引用进行的,所以会出现以下一般规则

  • 如果所有捕获的变量都是 Sync,则闭包是 Sync
  • 如果所有通过非唯一不可变引用捕获的变量都是 Sync,并且所有通过唯一不可变或可变引用、复制或移动捕获的值都是 Send,则闭包是 Send
  • 如果闭包没有通过唯一不可变或可变引用捕获任何值,并且它通过复制或移动捕获的所有值分别是 CloneCopy,则该闭包是 CloneCopy