Compiler bug? Two trait bounds that should be independent seem to get confused

This is the best MRE I could come up with:

struct A;
struct B;

impl From<B> for A {
    fn from(_: B) -> A {
        A
    }
}

trait V {
    type E;
}

trait W {
    type E;
}

struct X;

impl W for X {
    type E = B;
}

fn inner<T>(_: T)
where
    T: W,
    A: From<<T as W>::E>,
{
}

fn outer<T>(_: T)
where
    T: V,
    A: From<<T as V>::E>,
{
    inner(X);
}

(Playground)

This results in a compile-time error:

error[E0271]: type mismatch resolving `<X as W>::E == <T as V>::E`
  --> src/lib.rs:36:5
   |
36 |     inner(X);
   |     ^^^^^^^^ type mismatch resolving `<X as W>::E == <T as V>::E`
   |
note: expected this to be `<T as V>::E`
  --> src/lib.rs:21:14
   |
21 |     type E = B;
   |              ^
   = note: expected associated type `<T as V>::E`
                       found struct `B`
help: consider constraining the associated type `<T as V>::E` to `B`
   |
33 |     T: V<E = B>,
   |         +++++++

For more information about this error, try `rustc --explain E0271`.

It seems the compiler concludes that since A is bounded by From<T> on both functions, and one calls the other, that the two Ts must be the same, but this is false. There are two ways to make this compile:

  1. Remove the A: From<<T as V>::E> bound from outer.
  2. Change the bound to A: From<<T as V>::E> + From<<X as W>::E>, even though the additional bound shouldn't be required.
  3. Transform one of the bounds to the equivalent Into bound.

The fact that the first option allows the code to compile shows that the two Ts in From<T> are in fact totally independent -- there's just something about this specific pattern where the compiler seems to get confused and conclude they must be the same type.

Is there something I'm missing here or is this a bug? (Honestly, I have a strong suspicion I've seen this before and it was a bug, but if so then I can't come up with the search terms to find it again.)

In my real code, option 1 doesn't work since I need that bound, but option 2 is awkward because the W trait is private and the function is public.

4 Likes

I don't know why this is necessary, but if you explicitly specify the type parameter in the call to inner, it compiles:

-    inner(X);
+    inner::<X>(X);
4 Likes

Oh that's interesting, thanks for pointing that out! While a bit verbose, it at least limits the workaround to the function body, allowing me to keep the signature I want.

This issue seems to be equivalent. Your code compiles using the new trait solver like it does in the aforementioned issue.

Even if your code compiled in stable, I'd still use the equivalent Into bound instead of From since the former is strictly more general; but perhaps your actual code doesn't rely on From/Into but some other trait where this isn't possible.

4 Likes

Correct, the bounds are to use the ? operator, which uses From and not Into, so Into bounds can't work. (However, I'd expect the same confusion with two Into bounds anyway.)

Marking your comment as the solution since it answers my question of whether this is a compiler bug.

Interestingly the below code does not compile even when using the new trait solver. It emits error code E0308, but it's effectively the same problem where the compiler incorrectly expects the type parameters of the two functions to be related:

#![expect(dead_code, reason = "example")]
trait UnaryTraitConstructor<T: ?Sized> {}
impl<T: ?Sized, U: ?Sized> UnaryTraitConstructor<T> for U {}
fn inner<T>(_: T)
where
    u8: UnaryTraitConstructor<T>,
{
}
fn outer<T>()
where
    T: ?Sized,
    u8: UnaryTraitConstructor<T>,
{
    inner::<()>(());
    // Uncommenting below causes a compilation failure even
    // when using the new `trait` solver.
    //inner(());
}

There are a bunch of ways to tinker with above to see what does and does not compile and that many more when you add additional trait bounds and traits with associated types. I may create a new issue on GitHub seeing how above isn't solved with the new trait solver, and I can't fine an existing issue that illustrates this.

This one I believe.

1 Like