Bounds on associated types enforced too soon


#1

I am trying to write some code like this:

Link to the Playground

pub trait A<T> {
}

pub trait B {
    type X;
    type Y: A<Self::X>;
}

pub trait C {
    type X;
    type Y: B<X = Self::X>;
    // ^^^ This complains that <Self:Y as B>::Y does not implement A<Self::X>
    //     although Self::Y wouldn't be implementing B otherwise, now would it,
    //     given that Self::X == <Self::Y as B>::X.
}

I suspect it has to do with the type equality Self::X == <Self::Y as B>::X. However, I wish Rust evaluated these requirements on impl rather than on trait declaration, since there it would be more obvious what the types are.

On the #beginners forum, stephaneyfx provided this brilliant workaround Link to Playground:

pub trait C {
    type X;
    type BY: A<Self::X>;
    type Y: B<X = Self::X, Y = Self::BY>;
}

By adding BY with the same bound as B::Y has, rustc was appeased. The question still remains: whyyy :cry:?


#2

This is an interesting case. I suspect what’s happening is since B: Y references B: X the compiler wants to ensure that B: Y's bounds have been satisfied eagerly. This is in contrast to, say, something like:

trait B {
   type X;
   type Y: std::fmt::Debug;
   type Z: A<Self::Y>
}

Here there’s no relationship between X and Y/Z, although Y and Z have bounds. Compiler is happy with this formulation when you define trait C referencing B: X because you’ve said nothing about Y and Z in the process.

So I guess the question is whether the seeming inconsistency in when trait bounds are checked is an impl artifact or some principled design.

I think a github issue might be worthwhile to get someone from the language team to explain.


#3

Thank you for pointing this out! I will post on the internals thread, just to get the discussion started and to see if anyone has some insights.


#4

Is this what has been called eager vs lazy normalization? If so that seems to be something being worked on.