Help me understand a bound error with a `where` clause on a trait definition

Hi,

I have trouble understanding why this won't compile (playground link):

trait TestingA {
    type Error;
}

trait ErrorA {}

trait TestingB: TestingA
where
    Self::Error: ErrorA    
{}

fn testinga<T: TestingB>(){}

It leads to the following error:

   Compiling playground v0.0.1 (/playground)
error[E0277]: the trait bound `<T as TestingA>::Error: ErrorA` is not satisfied
  --> src/lib.rs:12:16
   |
12 | fn testinga<T: TestingB>(){}
   |                ^^^^^^^^ the trait `ErrorA` is not implemented for `<T as TestingA>::Error`
   |
note: required by a bound in `TestingB`
  --> src/lib.rs:9:18
   |
7  | trait TestingB: TestingA
   |       -------- required by a bound in this trait
8  | where
9  |     Self::Error: ErrorA    
   |                  ^^^^^^ required by this bound in `TestingB`
help: consider further restricting the associated type
   |
12 | fn testinga<T: TestingB>() where <T as TestingA>::Error: ErrorA{}
   |                            ++++++++++++++++++++++++++++++++++++

For more information about this error, try `rustc --explain E0277`.
error: could not compile `playground` (lib) due to previous error

The compiler suggestion does fix the issue, but I do not understand why this doesn't compile.

The TestingB definition requires the Self::Error: ErrorA bound, so I don't understand why T: TestingB does not automatically imply that T::Error: ErrorA. Am I misunderstanding how where clauses work on trait definitions?

In my case it's a significant issue because TestingB is actually a supper trait of 3 traits, so it would mean having 4 bounds everywhere, which is really verbose (and the purpose of TestingB is actually to have a single trait bound rather than 4, it does not define any actual behavior).

I was able to fix the issue by refactoring a bit (playground link), but I'm still confused by the initial error:

trait TestingA {
    type Error;
}

trait ErrorA {}

trait TestingB: TestingA<Error = <Self as TestingB>::Error>
 
{
    type Error: ErrorA;
}

fn testinga<T: TestingB>(){}

It's a known limitation of the compiler (Rust issue #20671, I think). A possible workaround is to specify the non-Self bounds explicitly:

trait TestingA {
    type Error;
}

trait ErrorA {}

trait TestingB: TestingA
where
    Self::Error: ErrorA    
{}

fn testinga<T: TestingB>()
where
    T::Error: ErrorA,
{}

(Playground)


Duplicating the associated type (like you did) is another workaround, that seems to work (I did something similar here). Not sure which is the better approach.

2 Likes

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.