# Playing with HRTBs

I decided to poke at the edges of what higher-ranked type bounds can and can’t do, and I thought the results might be interesting to some people here. In particular, the behaviors for `impl Trait` and `dyn Trait` are a bit surprising.

``````trait Trait where for<'a> &'a Self: Trait {
fn foo(self)->() where Self:Sized {()}
}

// This is ok
impl<'a, 'b, T:?Sized> Trait for &'a &'b T where &'b T:Trait {}

// This complains of not being general enough
// impl<'a, 'b:'a, T:?Sized> Trait for &'a &'b T where &'b T:Trait {}

// All of these are ok
impl<'a, T> Trait for &'a Vec<T> {}
impl<T> Trait for Vec<T> {}
impl<'a, T> Trait for &'a [T] {}

// This works (with arbitrary numbers of `&`s)
fn f1(x:&Vec<()>) {
x.foo();
}

// Typechecker infinite recursion
//fn f2(x:&impl Trait) {
//    x.foo()
//}

// This works, so Trait is object safe
fn f3(x:Vec<()>) {
let _:&dyn Trait = &x;
}

// Complains that `foo` cannot be invoked on a trait object
// but `dyn Trait` implements `Trait`
// which means `&dyn Trait` (which is Sized) *must* implement Trait per the where clause
//fn f3(x:&dyn Trait) {
//    (&&x).foo()
//}
``````

Errors:

``````   Compiling playground v0.0.1 (/playground)
warning: function is never used: `f1`
--> src/lib.rs:17:4
|
17 | fn f1(x:&Vec<()>) {
|    ^^
|
= note: `#[warn(dead_code)]` on by default

warning: function is never used: `f3`
--> src/lib.rs:27:4
|
27 | fn f3(x:Vec<()>) {
|    ^^

warning: 2 warnings emitted

Finished dev [unoptimized + debuginfo] target(s) in 0.31s

``````

There is indeed an issue / a limitation of the compiler here:

1. Adding a `where` clause is a "gained property" within the block that contains it, and an added "burden" for users / implementors.

• This is why you can add:
``````fn bar (self) where Self : Sized { self.foo(); }
``````
inside the trait without problem: `Self` is there, within the default implementation, some unknown `impl ?Sized + Trait` that also carries the `for<'any> &'any Self : Trait` and `Self : Sized` properties, hence allowing the usage of `.foo()`.
2. Outside of such block, thus, we are mostly dealing with an added burden.

Although sometimes, the Rust compiler is indeed smart enough to figure out a chain of causation (e.g., that `T : Trait` must imply that `for<'any> &'any T : Trait` since any implementor would have had that burden on them), so as to grant a property, this is not (yet) always the case. See the RFC for "implied bounds" for more info.

I suspect you are hitting this situation here.

3. Whatever the situation, `dyn Trait` is indeed special: you can create that type "whenever you want", and we'd expect `dyn Trait : Trait` to hold. But if you have never had the burden to `impl Trait for &'_ dyn Trait {}`, then for sure the Rust compiler will not invent that `impl` for you! So even if implied bounds or something along those lines were to work, I don't think we'll ever be able to have `dyn Trait : Trait` without the required `impl` on the reference type.

4. This makes it so `dyn Trait` may not be covered by `impl ?Sized + Trait`; but given such a type `T : ?Sized + Trait`, then implied bounds ought to give us that `for<'any> &'any T : Trait`.

5. Your example with the "infinite type-checking recursion" does show us that it is not yet the case. So the `impl Trait` sugar is unusable: we need to add the "property" ourselves, even if it may seem redundant:

``````trait Trait
where
for<'any> &'any Self : Trait,
{
fn foo (self)
where
Self : Sized,
{}
}

impl<'lt, T : ?Sized> Trait for &'_ &'lt T
where
&'lt T : Trait,
{}

fn check<ImplTrait : ?Sized + Trait> (x: &'_ ImplTrait)
where
for<'any> &'any ImplTrait : Trait,
{
x.foo();
}
``````
6. Another option, depending on your use case, is to get rid of the burden altogether with a super generic `impl`:

``````impl<T : ?Sized + Trait> Trait for &'_ T {}
``````

(at which point we can get rid of the `where` clause).

And then everything Just Works, but because we have changed the "rules of the game".

Yeah, that's just stupid, I hope that gets fixed soon, it can become quite annoying: implied lifetime bounds (such as `'inner : 'outer`) are currently not part of the initial set of constraints, so if you make them explicit, Rust considers you have lost generality .

These limitations are not really tied to HRTB, rather to `where` clauses on traits that are not of the form `where Self : ...` (since those can be treated as supertraits / superbounds).

1 Like

This might be worth adding to the object-safety rules: if there are any `where` clauses that constrain related types, it's should probably illegal to create a `dyn Trait` if those bounds aren't satisfied for the resulting object.

This is unfortunate as I originally hit this when trying to define a marker trait that served as a shorthand for a complicated set of type bounds. I suppose that'll have to be a macro's job until the implied bounds work gets more advanced.

That's the solution I ended up with, which doubled the number of methods in the trait (consuming + by ref), with a default implementation of the consuming method that dispatches to the reference one if it's not overridden.

Combined with the occasional `T might not live long enough` errors that force the lifetime bounds to be explicitly spelled out, this sometimes makes the `for<'a>` construction completely unusable. If I run into this again, I'll try to reduce it to something that can go in a bug report.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.