Description
Hello,
I have two traits, one that is to be manually implemented by the user and the other, if the first was implemented in a valid way, is automatically blanket implemented on the same type.
If I bound that the first trait must also implement the second, so the compiler points out invalid implementations of it, certain situations can cause associated types to mismatches when they are actually the same.
Minimal code to replicate:
trait Foo: Bar {
type X;
fn take_x(x: Self::X);
}
trait Bar {
type Y;
fn take_y(y: Self::Y);
}
impl<T> Bar for T
where
T: Foo, /*and other bounds to validate Foo impl*/
{
type Y = T::X;
fn take_y(y: Self::Y) {
T::take_x(y);
}
}
Causes this error:
Compiling playground v0.0.1 (/playground)
error[[E0308]](https://doc.rust-lang.org/stable/error_codes/E0308.html): mismatched types
--> src/lib.rs:17:19
|
17 | T::take_x(y);
| --------- ^ expected `Foo::X`, found `Bar::Y`
| |
| arguments to this function are incorrect
|
= note: expected associated type `<T as Foo>::X`
found associated type `<T as Bar>::Y`
= note: an associated type was expected, but a different one was found
note: associated function defined here
--> src/lib.rs:3:8
|
3 | fn take_x(x: Self::X);
| ^^^^^^
On the Playground, while the result is the same on Nightly as well.
Workaround 1: Bound at caller site instead
One fix is to not bound Bar
on Foo
.
trait Foo/*: Bar*/ {
type X;
fn take_x(x: Self::X);
}
That has the downside that the compiler cannot point out invalid implementations of Foo
before one attempts to use the type somewhere, where now the additional bound with + Bar
has to be enforced.
Workaround 2: Impl detail to trait bound
Another is to bound the types to match in the Foo
bound already:
trait Foo: Bar<Y = Self::X> {
type X;
fn take_x(x: Self::X);
}
//...
On the playground.
This compiles and might be a solution for some, depending how the actual bounds look like.
This also limits users who want to manually implement Bar
for their type where the associated types are not equal.
Failing workaround 1: Another generic in the impl
One could attempt to bring in another generic into the implementation, but this makes the compiler complain as well and also suggest a solution that is not valid Rust:
//...
impl<T, U> Bar for T
where
T: Foo<X = U>, /*and other bounds to validate Foo impl*/
{
type Y = U;
fn take_y(y: Self::Y) {
T::take_x(y);
}
}
Compiling playground v0.0.1 (/playground)
error[[E0308]](https://doc.rust-lang.org/stable/error_codes/E0308.html): mismatched types
--> src/lib.rs:17:19
|
11 | impl<T, U> Bar for T
| - this type parameter
...
17 | T::take_x(y);
| --------- ^ expected type parameter `U`, found associated type
| |
| arguments to this function are incorrect
|
= note: expected type parameter `U`
found associated type `<T as Bar>::Y`
note: associated function defined here
--> src/lib.rs:3:8
|
3 | fn take_x(x: Self::X);
| ^^^^^^
help: consider further restricting this bound
|
13 | T: Foo<X = U> + <Y = U>, /*and other bounds to validate Foo impl*/
| +++++++++
On the Playground and here with the invalid suggestion. The behavior is again the same on Nightly here.
Failing workaround 2: Supertrait
Another attempt is using a super trait, but bounding Foo
causes a cyclic recursion again, and bounding only Bar
results in the super trait to be basically an alias of the top code with the same error.
pub trait FooAndBar: Foo + Bar {}
impl<T> FooAndBar for T where T: Foo + Bar {}
trait Foo: FooAndBar {
type X;
fn take_x(x: Self::X);
}
//...
If one moves the bounds of the super trait to the impl, it compiles, but lacks the ability to access the implementations of Foo
and Bar
now. One would have to mirror both traits in here and this will cause the initial problem again, now in this third trait.
My assumption is that the compiler within the blanket impl assumes that the trait is already implemented and calling T::take_x(y);
considers an unspecific implementation where the types might not match.