Trait Composition and Associated Types


#1

Hello everybody!

I have a question about the use of associated types in traits and then the specification of those associated types in the declaration of a new trait which composes others. Generally, you could suppose that I have three traits: T1, T2, and T3. T1 and T2 each have an associated type T. T3 is defined as the composition of T1 and T2 with some extra methods, and also an associated type T, which must be the same as it is for both T1 and T2.

I’ve found that I can write code like the following:

trait Thing1 {
    type Token;
    
    fn do_one(&self) -> <Self as Thing1>::Token;
}

trait Thing2 {
    type Token;
    
    fn do_two(&self, &<Self as Thing2>::Token);
}

trait Thing3: Thing1<Token=<Self as Thing3>::Token> + Thing2<Token=<Self as Thing3>::Token> {
    type Token;
    
    fn do_three(&self, &<Self as Thing3>::Token);
}

This appears to have the effect I’m looking for. Implementations of Thing3 with some Token type must also implement Thing1 and Thing2 using the same Token type as for Thing3.

If instead of writing <Self as Thing3>::Token, I declared Thing3 as

trait Thing3: Thing1<Token=Self::Token> + Thing2<Token=<Self as Thing3>::Token>

then I get the following error.

error[E0391]: unsupported cyclic reference between types/traits detected
  --> <anon>:13:28
   |
13 | trait Thing3: Thing1<Token=Self::Token> + Thing2<Token=<Self as Thing3>::Token> {
   |                            ^^^^^^^^^^^ cyclic reference
   |
   = note: the cycle begins when computing the supertraits of `Thing3`...
   = note: ...which then again requires computing the supertraits of `Thing3`, completing the cycle.

If instead of writing <Self as Thing3> I just wrote Thing3 like so

trait Thing3: Thing1<Token=Thing3::Token> + Thing2<Token=<Self as Thing3>::Token>

then I get the following error

error[E0223]: ambiguous associated type
  --> <anon>:13:28
   |
13 | trait Thing3: Thing1<Token=Thing3::Token> + Thing2<Token=<Self as Thing3>::Token> {
   |                            ^^^^^^^^^^^^^ ambiguous associated type
   |
   = note: specify the type using the syntax `<Type as Thing3>::Token`

Both of these errors make sense to me, and I understand how ambiguity/a cycle could occur.

This leads me to think that my approach seems unnecessarily convoluted and I must be straying from idiomatic Rust to be forced to write, what I think is, such unwieldy code. Is there an alternative means I could take to defining my traits such that I achieve the following goals without such a mess?

  1. I want to keep the three traits- each one has value on its own.
  2. I want the former two traits to have a relatively large degree of freedom to specify which associated type to use, independent of the other.
  3. I need the third type to require the former two have the same associated type.

I assume there are also some compromises I could make on these points and would really like to get people’s opinions. This question has less to do with making the code run as it does finding an idiomatic solution.


#2

I don’t think there’s anything really wrong with your original version, but you could simplify it a bit by removing the associated type from Thing3:

trait Thing3: Thing1 + Thing2<Token=<Self as Thing1>::Token> {
    fn do_three(&self, &<Self as Thing1>::Token);
}