规则
生命周期省略规则
函数或方法的参数的生命周期被称为 输入生命周期(input lifetimes),而返回值的生命周期被称为 输出生命周期(output lifetimes)。
编译器采用三条规则来判断引用何时不需要明确的注解。第一条规则适用于输入生命周期,后两条规则适用于输出生命周期。如果编译器检查完这三条规则后仍然存在没有计算出生命周期的引用,编译器将会停止并生成错误。这些规则适用于 fn 定义,以及 impl 块。
- 每一个是引用的参数都有它自己的生命周期参数。换句话说就是,有一个引用参数的函数有一个生命周期参数:fn foo<'a>(x: &'a i32),有两个引用参数的函数有两个不同的生命周期参数,fn foo<'a, 'b>(x: &'a i32, y: &'b i32).
- 如果只有一个输入生命周期参数,那么它被赋予所有输出生命周期参数:fn foo<'a>(x: &'a i32) -> &'a i32。
- 如果方法有多个输入生命周期参数并且其中一个参数是 &self 或 &mut self,说明是个对象的方法(method),那么所有输出生命周期参数被赋予 self 的生命周期。
排除省略(elision)的情况,带上生命周期的函数签名有一些限制:
- 任何引用都必须拥有标注好的生命周期。
- 任何被返回的引用都必须有和某个输入量相同的生命周期或是静态类型(static)
闭包捕获参数
闭包可以通过三种方式捕获其环境,他们直接对应函数的三种获取参数的方式:获取所有权,可变借用和不可变借用。这三种捕获值的方式被编码为如下三个 Fn trait。其受限制程度按以下顺序递减:
- Fn:表示捕获方式为通过引用(&T)的闭包
- FnMut:表示捕获方式为通过可变引用(&mut T)的闭包
- FnOnce:表示捕获方式为通过值(T)的闭包
顺序之所以是这样,是因为 &T 只是获取了不可变的引用,&mut T 则可以改变变量,T 则是拿到了变量的所有权而非借用。对闭包所要捕获的每个变量,在满足使用需求的前提下尽量以限制最多的方式捕获。
例如用一个类型说明为 FnOnce 的闭包作为参数。这说明闭包可能采取 &T,&mut T 或 T 中的一种捕获方式,但编译器最终是根据所捕获变量在闭包里的使用情况决定捕获 方式。
这是因为如果能以移动的方式捕获变量,则闭包也有能力使用其他方式借用变量。注意 反过来就不再成立:如果参数的类型说明是 Fn,那么不允许该闭包通过 &mut T 或 T 捕获变量。
// 该函数将闭包作为参数并调用它。 fn apply<F>(f: F) where // 闭包没有输入值和返回值。 F: FnOnce() { // ^ 试一试:将 `FnOnce` 换成 `Fn` 或 `FnMut`。 f(); } // 输入闭包,返回一个 `i32` 整型的函数。 fn apply_to_3<F>(f: F) -> i32 where // 闭包处理一个 `i32` 整型并返回一个 `i32` 整型。 F: Fn(i32) -> i32 { f(3) } fn main() { use std::mem; let greeting = "hello"; // 不可复制的类型。 // `to_owned` 从借用的数据创建有所有权的数据。 let mut farewell = "goodbye".to_owned(); // 捕获 2 个变量:通过引用捕获 `greeting`,通过值捕获 `farewell`。 let diary = || { // `greeting` 通过引用捕获,故需要闭包是 `Fn`。 println!("I said {}.", greeting); // 下文改变了 `farewell` ,因而要求闭包通过可变引用来捕获它。 // 现在需要 `FnMut`。 farewell.push_str("!!!"); println!("Then I screamed {}.", farewell); println!("Now I can sleep. zzzzz"); // 手动调用 drop 又要求闭包通过值获取 `farewell`。 // 现在需要 `FnOnce`。 mem::drop(farewell); }; // 以闭包作为参数,调用函数 `apply`。 apply(diary); // 闭包 `double` 满足 `apply_to_3` 的 trait 约束。 let double = |x| 2 * x; println!("3 doubled: {}", apply_to_3(double)); }