How to bound an associated type's associated type


I'm using a library defining a trait

trait LibraryTrait {
    type Foo;

I want to define my own trait, also with an associated type with a bound on LibraryTrait

trait MyTrait {
    type Bar: LibraryTrait;

However I need to be able to also add a bound on LibraryTrait::Foo in my associated type Bar.
It seems where clause are not permitted in associated types, how should I do this?
Thanks for you help.

1 Like
trait MyTrait where <Self::Bar as LibraryTrait>::Foo: Clone {
    type Bar: LibraryTrait;

Huh, that's pretty weird-looking. Might we get general where for associated types as part of the GAT stabilization effort?

Spoilers: I do a long detour through GATs that doesn't matter, and then wrap up with a more-verbose-to-implement but also more-ergonomic-to-read alternative at the end. Skip to the end if you just want an alternative answer to the OP and not a long winded "GATs don't really help." (CC @pandamatic)

There is a general where for GATs but we have to be sort of careful and technical about what that means.

trait Foo {
    type Bar<T: Grok>: Hack where T: Ick;

Here, Grok and Ick are both where-clauses. They apply to the inputs of the GAT and are enforced at the point of normalization, and the implementation of Foo can rely on them holding.

But Hack is a bound on the GAT itself, just like we can have bounds directly on (plain) associated types today. The meaning is the same -- those bounds are translated to where clauses on the trait.

// Made up syntax
trait Foo where for<T> <Self as Foo>::Bar<T>: Hack { ... }

These have to be proven by the implementation.

Alright, back to our situation. We want a more ergonomic way to express the "where clause on trait" variety of bound that we already have, but for something too complicated to apply directly to the associated type. Currently, the other way to express bounds are where clauses. But where clauses on associated items like GATs (or functions) are semantically different.

Does the difference matter? I.e. can we give up on the "behaves exactly the same" but still get the bounds we want using where clauses?

I'm not aware of a way to do so yet, so I can't even experiment and see. With GATs, we can spell out a path to itself in a where clause:

trait MyTrait {
    type Bar<T>: LibraryTrait
        where <Self::Bar<T> as LibraryTrait>::Foo: Clone;

That even compiles... on its own. But if you try to actually use it, you trigger E0275: Recursive trait requirement.

Even if this was special-cased to be a where clause on the trait and not on the associated type or GAT, it's not much of an ergonomic improvement. We've just moved it around. That said, special-casing it may be considered reasonable, analogous to this code which is valid today:

trait Foo: Super {}
trait Foo where Self: Super {} // same thing

So, to summarize, I don't think GATs as they are implemented today gain us much. They could be updated to gain us a little, but just a little. All out of luck without an RFC for new syntax then?

No, there's another alternative: Associated type bounds (tracking issue).

trait MyTrait {
    type Bar: LibraryTrait<Foo: Clone>;

Sadly, I doubt it's close to stabilization. This case doesn't work on nightly.

Taking inspiration from that RFC though, here's an alternative that's stable today:

trait MyTrait {
    type Foo: Clone;
    type Bar: LibraryTrait<Foo = Self::Foo>;

It's more verbose to use, but certainly easier to read. I probably should have thought of this earlier -- it's the pattern IntoIterator uses.


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.