Why are supertrait bounds other than the first not recognized on an associated type?
Asked Answered
U

1

5

This snippet is valid in Rust 1.26.1:

use std::ops::AddAssign;

trait Trait
where
    for<'a> Self: AddAssign<Self> + AddAssign<&'a Self> + Sized,
{
}

trait Trait2 {
    type Associated: Trait;

    fn method(u32) -> Self::Associated;
}

fn func<T2: Trait2>() {
    let mut t = T2::method(1);
    let t2 = T2::method(2);
    t += &t2;
}

Notice that Trait implements both AddAssign<Self> and AddAssign<&'a Trait> (in that order, which is important later). Therefore, in func we know that both t += t2 and t += &t2 should be valid. As seen on the playground, t += &t2 is valid, but using t += t2 isn't:

error[E0308]: mismatched types
  --> src/main.rs:19:10
   |
19 |     t += t2;
   |          ^^
   |          |
   |          expected reference, found associated type
   |          help: consider borrowing here: `&t2`
   |
   = note: expected type `&<T2 as Trait2>::Associated`
              found type `<T2 as Trait2>::Associated`

I read this error as the compiler not recognizing that AddAssign<Self> is implemented for T::Associated, which is clearly wrong, as it implements Trait, which requires AddAssign<Self>.

If we change the order of the AddAssign bounds on Trait then the opposite holds: t += t2 is valid while t += &t2 isn't.

A quick fix for the problem is to make func generic over both traits:

fn func<T: Trait, T2: Trait2<Associated = T>>() {
    let mut t = T2::method(1);
    let t2 = T2::method(2);
    t += t2;
}

This shouldn't be necessary; the compiler can recognize one of the AddAssigns, why not the other? It seems the last bound is the one to be recognized.

My first suspicion was this this has something to do with dynamic dispatch. I ruled it out since the order of the bounds shouldn't matter even in dynamic dispatch. I don't even think it uses it, since all types are known at compile-time using monomorphisation.

My current suspicion is a compiler bug where the typechecker doesn't account for generics on trait bounds when it is an associated type. It is easy to imagine such a specific case being overlooked.

What is going here?

Undrape answered 2/6, 2018 at 20:29 Comment(2)
I have never seen anything in the documentation to suggest that the order should ever be important, so this feels like a bug.Truc
"dynamic dispatch. I don't even think it uses it" — Correct. There is no dynamic dispatch here.Truc
S
7

This is a known bug (or a combination of a few):

  1. Higher-ranked trait bounds on associated types are not elaborated (#50346).
  2. where clauses are only elaborated for supertraits, and not other things (#20671)
  3. Constraints on associated types declared in subtraits do not propagate. (#32722)
  4. Unrecognized associated type bound on another associated type (#24159)

The workaround is to restate the bounds at every usage site:

fn func<T2>()
where
    T: Trait2,
    T::Associated: Trait,
{
    let mut t = T::method(1);
    let t2 = T::method(2);
    t += &t2;
    t += t2;
}

This should be addressed when the type system moves from its ad hoc implementation to Chalk, a more principled solver for the types of problems a complicated type system creates.

Sallet answered 2/6, 2018 at 23:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.