Skip to content

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

Open
@urben1680

Description

@urben1680

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-associated-itemsArea: Associated items (types, constants & functions)C-bugCategory: This is a bug.T-typesRelevant to the types team, which will review and decide on the PR/issue.fixed-by-next-solverFixed by the next-generation trait solver, `-Znext-solver`.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions