闭包表达式

closure-expr.md
commit: d23f9da8469617e6c81121d9fd123443df70595d
本章译文最后维护日期:2021-5-6

句法
ClosureExpression :
   move?
   ( || | | ClosureParameters? | )
   (Expression | -> TypeNoBounds BlockExpression)

ClosureParameters :
   ClosureParam (, ClosureParam)* ,?

ClosureParam :
   OuterAttribute* PatternNoTopAlt ( : Type )?

闭包表达式,也被称为 lambda表达式或 lambda,它定义了一个闭包类型,并把此表达式求值计算为该类型的值。 闭包表达式的句法规则为:先是一个可选的 move关键字,后跟一对管道定界符(|)封闭的逗号分割的被称为闭包参数(closure parameters)模式列表(每个闭包参数都可选地通过 : 后跟其类型),再可选地通过 -> 后跟一个返回类型,最后是被称为闭包体操作数(closure body operand) 的表达式。 代表闭包参数的每个模式后面的可选类型是该模式的类型标注(type annotations)。 如果存在返回类型,则闭包体表达式必须是一个普通的(表达式)。

闭包表达式本质是将一组参数映射到参数后面的表达式的函数。 与 let绑定一样,闭包参数也是不可反驳型模式的,其类型标注是可选的,如果没有给出,则从上下文推断。 每个闭包表达式都有一个唯一的匿名类型。

特别值得注意的是闭包表达式能捕获它们被定义时的环境中的变量(capture their environment),而普通的函数定义则不能。 如果没有关键字 move,闭包表达式将[推断它该如何从其环境中捕获每个变量][infers how it captures each variable from its environment],它倾向于通过共享引用来捕获,从而有效地借用闭包体中用到的所有外部变量。 如果有必要,编译器会推断出应该采用可变引用,还是应该从环境中移动或复制值(取决于这些变量的类型)。 闭包可以通过前缀关键字 move 来强制通过复制值或移动值的方式捕获其环境变量。 这通常是为了确保当前闭包的生存期类型为 'static

编译器将通过闭包对其捕获的变量的处置方式来确定此闭包类型将实现的[闭包trait][closure traits]。 如果所有捕获的类型都实现了 Send 和/或 Sync,那么此闭包类型也实现了 Send 和/或 Sync。 这些存在这些 trait,函数可以通过泛型的方式接受各种闭包,即便闭包的类型名无法被确切指定。

闭包 trait 的实现

当前闭包类型实现哪一个闭包trait 依赖于该闭包如何捕获变量和这些变量的类型。 了解闭包如何和何时实现 FnFnMutFnOnce 这三类 trait,请参看调用trait 和自动强转那一章。 如果所有捕获的类型都实现了 Send 和/或 Sync,那么此闭包类型也实现了 Send 和/或 Sync

Example

示例

在下面例子中,我们定义了一个名为 ten_times 的函数,它接受高阶函数参数,然后我们传给它一个闭包表达式作为实参并调用它。 之后又定义了一个使用移动语义从环境中捕获变量的闭包表达式来供该函数调用。

#![allow(unused)]
fn main() {
fn ten_times<F>(f: F) where F: Fn(i32) {
    for index in 0..10 {
        f(index);
    }
}

ten_times(|j| println!("hello, {}", j));
// 带类型标注 i32
ten_times(|j: i32| -> () { println!("hello, {}", j) });

let word = "konnichiwa".to_owned();
ten_times(move |j| println!("{}, {}", word, j));
}

闭包参数上的属性

闭包参数上的属性遵循与常规函数参数上相同的规则和限制。