Skip to content

22.5 闭包与生命周期

当使用闭包做参数或返回值的时候,生命周期会变得更加复杂。假设有下面这段代码:

rust
fn calc_by<'a, F>(var: &'a i32, f: F) -> i32
    where F: Fn(&'a i32) -> i32
{
    f(var)
}

fn main() {
    let local = 10;
    let result = calc_by(&local, |i| i*2);
    println!("{}", result);
}

这段代码可以编译通过。但是,假如我们把 calc_by 的函数体稍微改一下,变成下面这样:

rust
let local = *var;
f(&local)

就不能编译通过了。

但是,如果我们把所有的生命周期标记去掉,变成这样:

rust
fn calc_by<F>(var: &i32, f: F) -> i32
    where F: Fn(&i32) -> i32
{
    let local = *var;
    f(&local)
}

fn main() {
    let local = 10;
    let result = calc_by(&local, |i| i*2);
    println!("{}", result);
}

这时所有的借用生命周期由编译器自动补全,又可以编译通过了。这说明,我们之前对这个 calc_by 函数手写的生命周期标记有问题。那我们手动来补全这些标记,应该怎么做呢?

在这里我们只能使用“高阶生命周期约束”,即高阶特征约束(Higher-Ranked Trait Bounds)(HRTBs) 的表示方法:

rust
fn calc_by<'a, F>(var: &'a i32, f: F) -> i32
    where for<'f> F: Fn(&'f i32) -> i32
{
    let local = *var;
    f(&local)
}

注意 F 的约束条件,其中 for<'f> 是高阶约束的标志,声明了一个通用生命周期 'f。这样写表示的意思是,Fn 的输入参数摆脱外部参数生命周期的限制,适配任意合法的生命周期'f。这个生命周期和另外一个参数 var 的生命周期没有半点关系。

这才是这段代码正确的生命周期标记方式。如果我们不手动标记出来,编译器会巧妙地创建了一些隐式的高阶生命周期参数边界(HRTBs)。

谈到“高阶”这两个字,很多朋友会想到高阶类型(Higher-Kinded Types)。这里的高阶生命周期确实跟高阶类型有很多相似之处,Rust 也确实在思考如何引入高阶类型这个问题。到目前为止,for<'a> Fn(&'a Arg) -> &'a Ret 这样的语法,只能用于生命周期参数,不能用于任意泛型类型。

Released under the MIT License