Higher-Rank Trait Bounds (HRTBs)

Rust’s Fn traits are a little bit magic. For instance, we can write thefollowing code:

  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. }

If we try to naively desugar this code in the same way that we did in thelifetimes section, we run into some trouble:

  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<'a>(&'a self) -> &'a u8 {
  9. (self.func)(&self.data)
  10. }
  11. }
  12. fn do_it<'b>(data: &'b (u8, u16)) -> &'b u8 { &'b data.0 }
  13. fn main() {
  14. 'x: {
  15. let clo = Closure { data: (0, 1), func: do_it };
  16. println!("{}", clo.call());
  17. }
  18. }

How on earth are we supposed to express the lifetimes on F‘s trait bound? Weneed to provide some lifetime there, but the lifetime we care about can’t benamed until we enter the body of call! Also, that isn’t some fixed lifetime;call works with any lifetime &self happens to have at that point.

This job requires The Magic of Higher-Rank Trait Bounds (HRTBs). The way wedesugar this is as follows:

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

(Where Fn(a, b, c) -> d is itself just sugar for the unstable real Fntrait)

for<'a> can be read as “for all choices of 'a“, and basically produces aninfinite list of trait bounds that F must satisfy. Intense. There aren’t manyplaces outside of the Fn traits where we encounter HRTBs, and even forthose we have a nice magic sugar for the common cases.