What's going on with Higher Rank Trait Bounds


#1

This is an example using HRTB:

struct S {
	var1: i32,
	var2: i32,
}

impl S
{
    fn extract<F>(&self, f: F) -> &i32
      where F: Fn(&S) -> &i32
    {
        f(self)
    }
}

fn main() {
    let var = S { var1: 1, var2: 2};
    let ptr_to_var1 = var.extract(| s | &s.var1);
    println!("{}", ptr_to_var1);
}

We know that the where clause in extract function actually means: where F: for <'f> Fn(&'f S) -> &'f i32.

But if I wrote as below, it still compiles by latest version of rustc [rustc 1.21.0-nightly (c11f689d2 2017-08-29)].

impl S
{
    fn extract<'a, F>(&'a self, f: F) -> &'a i32
      where F: Fn(&'a S) -> &'a i32
    {
        f(self)
    }
}

fn ext(s: &S) -> &i32 { &s.var2 }

fn main() {
    let var = S { var1: 1, var2: 2};
    let ptr_to_var1 = var.extract(ext);
    println!("{}", ptr_to_var1);
}

And I remember that this piece of code was rejected by compiler several months ago. I don’t know what’s happened in this period, could anyone can explain that? When should I use a HRTB while not using lifetime ellision?


#2

The 2nd version happens to compile due to normal lifetimes of arguments - caller (i.e. main) owns the S value and selects the lifetime for the struct (the 'a lifetime) that fits.

To see the need for HRTB try to create a fresh S inside extract and pass that to the closure - that should fail without HRTB.


#3

Thank you. Your test case is very helpful for me to understand HRTB.

I’m still curious why my test case doesn’t compile several months ago. Does that mean there was a bug before and now it is fixed?


#4

I tried running both of your code samples with Rust 1.0.0 (from May 2015) and they both worked. Can you remember anything that was different in the code before?


#5

Thanks. There must be something important I misremembered. Maybe the lifetime was associated with the struct but not the method.

Thank you all ! I think I understand HRTB better than before.