Skip to content

Fulfilling trait bound with blanket impl can cause associated type mismatch #112583

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
urben1680 opened this issue Jun 13, 2023 · 0 comments
Open
Labels
A-associated-items Area: Associated items (types, constants & functions) C-bug Category: This is a bug. fixed-by-next-solver Fixed by the next-generation trait solver, `-Znext-solver`. T-types Relevant to the types team, which will review and decide on the PR/issue.

Comments

@urben1680
Copy link

urben1680 commented Jun 13, 2023

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);
}

On the playground.

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.

@urben1680 urben1680 added the C-bug Category: This is a bug. label Jun 13, 2023
@compiler-errors compiler-errors added fixed-by-next-solver Fixed by the next-generation trait solver, `-Znext-solver`. A-associated-items Area: Associated items (types, constants & functions) T-types Relevant to the types team, which will review and decide on the PR/issue. labels Jun 13, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-associated-items Area: Associated items (types, constants & functions) C-bug Category: This is a bug. fixed-by-next-solver Fixed by the next-generation trait solver, `-Znext-solver`. T-types Relevant to the types team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests

2 participants