Bounds on associated types enforced too soon

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:?

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.

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.

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

1 Like