高阶 Trait 边界 (HRTB)

Rust 的 Fn trait 有点神奇。例如,我们可以编写以下代码

struct Closure<F> {
    data: (u8, u16),
    func: F,
}

impl<F> Closure<F>
    where F: Fn(&(u8, u16)) -> &u8,
{
    fn call(&self) -> &u8 {
        (self.func)(&self.data)
    }
}

fn do_it(data: &(u8, u16)) -> &u8 { &data.0 }

fn main() {
    let clo = Closure { data: (0, 1), func: do_it };
    println!("{}", clo.call());
}

如果我们尝试像在生命周期部分那样简单地对这段代码进行脱糖,我们会遇到一些麻烦

// NOTE: `&'b data.0` and `'x: {` is not valid syntax!
struct Closure<F> {
    data: (u8, u16),
    func: F,
}

impl<F> Closure<F>
    // where F: Fn(&'??? (u8, u16)) -> &'??? u8,
{
    fn call<'a>(&'a self) -> &'a u8 {
        (self.func)(&self.data)
    }
}

fn do_it<'b>(data: &'b (u8, u16)) -> &'b u8 { &'b data.0 }

fn main() {
    'x: {
        let clo = Closure { data: (0, 1), func: do_it };
        println!("{}", clo.call());
    }
}

我们到底应该如何在 F 的 trait 边界上表达生命周期?我们需要在那里提供一些生命周期,但是我们关心的生命周期在我们进入 call 的主体之前无法命名!此外,这并不是某个固定的生命周期;call 可以处理 &self 在那时碰巧拥有的任何生命周期。

这项工作需要高阶 Trait 边界 (HRTB) 的魔力。我们对它进行脱糖的方式如下

where for<'a> F: Fn(&'a (u8, u16)) -> &'a u8,

或者

where F: for<'a> Fn(&'a (u8, u16)) -> &'a u8,

(其中 Fn(a, b, c) -> d 本身只是对不稳定的真实 Fn trait 的语法糖)

for<'a> 可以理解为“对于 'a 的所有选择”,并且基本上会生成 F 必须满足的 trait 边界的无限列表。厉害吧。除了 Fn trait 之外,我们很少遇到 HRTB,即使对于那些情况,我们也为常见情况提供了很好的语法糖。

总而言之,我们可以更明确地重写原始代码,如下所示

struct Closure<F> {
    data: (u8, u16),
    func: F,
}

impl<F> Closure<F>
    where for<'a> F: Fn(&'a (u8, u16)) -> &'a u8,
{
    fn call(&self) -> &u8 {
        (self.func)(&self.data)
    }
}

fn do_it(data: &(u8, u16)) -> &u8 { &data.0 }

fn main() {
    let clo = Closure { data: (0, 1), func: do_it };
    println!("{}", clo.call());
}