Higher-Rank Trait Bounds (HRTBs)

Rust 的Fn trait 有一些黑魔法,例如,我们可以写出下面的代码:

  1. struct Closure<F> {
  2. data: (u8, u16),
  3. func: F,
  4. }
  5. impl<F> Closure<F>
  6. where F: Fn(&(u8, u16)) -> &u8,
  7. {
  8. fn call(&self) -> &u8 {
  9. (self.func)(&self.data)
  10. }
  11. }
  12. fn do_it(data: &(u8, u16)) -> &u8 { &data.0 }
  13. fn main() {
  14. let clo = Closure { data: (0, 1), func: do_it };
  15. println!("{}", clo.call());
  16. }

如果我们试图天真地用与生命周期部分相同的方式来对这段代码进行解语法糖,我们会遇到一些麻烦:

  1. // NOTE: `&'b data.0` and `'x: {` is not valid syntax!
  2. struct Closure<F> {
  3. data: (u8, u16),
  4. func: F,
  5. }
  6. impl<F> Closure<F>
  7. // where F: Fn(&'??? (u8, u16)) -> &'??? u8,
  8. {
  9. fn call<'a>(&'a self) -> &'a u8 {
  10. (self.func)(&self.data)
  11. }
  12. }
  13. fn do_it<'b>(data: &'b (u8, u16)) -> &'b u8 { &'b data.0 }
  14. fn main() {
  15. 'x: {
  16. let clo = Closure { data: (0, 1), func: do_it };
  17. println!("{}", clo.call());
  18. }
  19. }

我们究竟应该如何表达F的 trait 约束上的生命周期?我们需要在那里提供一些生命周期,但是我们关心的生命周期在进入call的主体之前是不能被命名的! 而且,这并不是什么固定的生命周期;call可以与&self在这一时刻上的任一生命周期一起使用。

要完成这个事情,需要使用到高阶 Trait 约束(HRTB)的魔力。我们的解语法糖方式如下:

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

或者:

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

(其中Fn(a, b, c) -> d本身只是不稳定的真正的*Fn特性的语法糖)

for<'a>可以理解为“对于所有'a的可能”,并且基本上产生一个无限的 F 必须满足的 trait 约束的列表。不过不用紧张,在Fn trait 之外,我们遇到 HRTB 的地方不多,即使是那些地方,我们也有一个很好的魔法糖来处理普通的情况。

最终,我们可以把原本的代码重写成更加显式的样子:

  1. struct Closure<F> {
  2. data: (u8, u16),
  3. func: F,
  4. }
  5. impl<F> Closure<F>
  6. where for<'a> F: Fn(&'a (u8, u16)) -> &'a u8,
  7. {
  8. fn call(&self) -> &u8 {
  9. (self.func)(&self.data)
  10. }
  11. }
  12. fn do_it(data: &(u8, u16)) -> &u8 { &data.0 }
  13. fn main() {
  14. let clo = Closure { data: (0, 1), func: do_it };
  15. println!("{}", clo.call());
  16. }