Yes, you need to use HRTBs when the lifetime elision rules for HRTBs fail (which they don't in your example) and you can't use a generic lifetime instead. Here is an example where you can't elide the lifetime on PrintWithSuffix and also can't pass a generic lifetime 'a, as the reference we pass to print_with_suffix lives only as long as the function lives (and not for any generic lifetime 'a we slap onto fn foo<'a, T>):
trait PrintWithSuffix<'a> {
fn print_with_suffix(&self, suffix: &'a u8);
}
impl<'a, T: std::fmt::Display> PrintWithSuffix<'a> for T {
fn print_with_suffix(&self, x: &'a u8) {
println!("{self}{x}");
}
}
fn foo<T>(x: T) where for<'a> T: PrintWithSuffix<'a> {
let y = &0; // <-- unnamable lifetime
x.print_with_suffix(y);
}
fn main() {
foo("hello");
}
Of course, I just added the lifetime to PrintWithSuffix as an example, it is completely unnecessary. Just imagine it needs to be there (I had a hard time to come up with an example, my bad), then you need to use HRTBs if you want to pass a reference to a local variable.
Higher-ranked lifetime elision is a feature of Fn bounds (and Fn trait object types and fn function pointer types). So if you have a trait that is not one of those, and have a lifetime in the parameters that you need to be higher-ranked, that's a situation where you'll need the explicit syntax.