Hi, I wanted to see what happens when HRTBs are used in more complex cases such as trait objects.
I evolved the example from the nomicon with a vector F: for<'a> Fn(&'a T).
I'd now like to try and find an implementation that works via trait object but didn't found a way to make it work
struct Closure<T> {
data: [u8;10],
cbs: Vec<Box<dyn for<'b> Fn(&'b T)>>
}
impl Closure<Data<'_>>
{
fn call(&self) {
for f in self.cbs.iter() {
let d = Data(&self.data, 0_u16);
(f)(&d);
}
}
}
#[derive(Debug)]
pub struct Data<'a>(&'a [u8], u16);
fn main() {
let c1 = |v:&Data | {
println!("CLOSURE 1 {:?}", v);
};
let c2 = |v:&Data | {
println!("CLOSURE 2 {:?}", v);
};
let clo = Closure {
data: [1_u8; 10],
cbs: vec![Box::new(c1), Box::new(c2)]
};
clo.call();
}
Compiling playground v0.0.1 (/playground)
error: lifetime may not live long enough
--> src/main.rs:13:13
|
10 | fn call(&self) {
| -----
| |
| let's call the lifetime of this reference `'1`
| has type `&Closure<Data<'2>>`
...
13 | (f)(&d);
| ^^^^^^^ argument requires that `'1` must outlive `'2`
error: could not compile `playground` (bin "playground") due to 1 previous error
If I introduce the bound '1: '2, It will not be ok with the lifetime of the closure struct with will not leave long enough.
Have I missed something?
The problem isn't with the trait object or the HRTB, but with your type parameter and impl.
impl Closure<Data<'_>>
This says that we're implementing for Closure<Data<'2>>, for all lifetimes '2 (using the name the compiler assigned).
let d = Data(&self.data, 0_u16);
(f)(&d);
Then you try to call the function with a reference to a local variableData<'1>, which is not and can never be a Data<'2>, because local variables always have shorter lifetimes than any lifetime parameter of the fn or impl. The HRTB doesn't help you because it doesn't interact with the lifetime parameter of Data at all; T = Data<'2>, not Data<'a>. To make it do so, you'd need to give up the generic T and mention Data in the Fn signature:
Something mentioned in @kpreid's explanation that I'd like to especially highlight is that type parameters, such as the T in struct Closure<T>, always represent a single type. Additionally, types that differ by lifetime are distinct types.
That's why T can never represent Data<'x> for any lifetime 'x. It can only represent Data<'y> for one particular lifetime 'y.
Side note: The Fn(..) traits have special higher-ranked sugar syntax, such that these are the same thing:
Thank you very much for the detailed explanation
I'd like to have a more detailed view of how lifetimes work in relation to the compiler. Would you have any books exercices or resources to recommend?
I've written some things here, but it doesn't cover everything. It doesn't really cover the HRTB type scenarios of this topic for example. I don't know of a good singular resource for that yet.