Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Having separate Trait and Projection bounds

Given T: Foo<AssocA = u32, AssocB = i32> where-bound, we currently lower it to a Trait(Foo<T>) and separate Projection(<T as Foo>::AssocA, u32) and Projection(<T as Foo>::AssocB, i32) bounds. Why do we not represent this as a single Trait(Foo[T], [AssocA = u32, AssocB = u32] bound instead?

The way we prove Projection bounds directly relies on proving the corresponding Trait bound: old solver new solver.

It feels like it might make more sense to just have a single implementation which checks whether a trait is implemented and returns (a way to compute) its associated types.

This is unfortunately quite difficult, as we may use a different candidate for norm than for the corresponding trait bound. See alias-bound vs where-bound and global where-bound vs impl.

There are also some other subtle reasons for why we can’t do so. The most stupid is that for rigid aliases, trying to normalize them does not consider any lifetime constraints from proving the trait bound. This is necessary due to a lack of assumptions on binders - https://github.com/rust-lang/trait-system-refactor-initiative/issues/177 - and should be fixed longterm.

A separate issue is that right now, fetching the type_of associated types for Trait goals or in shadowed Projection candidates can cause query cycles for RPITIT. See https://github.com/rust-lang/trait-system-refactor-initiative/issues/185.

There are also slight differences between candidates for some of the builtin impls, these do all seem generally undesirable and I consider them to be bugs which would be fixed if we had a unified approach here.

Finally, not having this split makes lowering where-clauses more annoying. With the current system having duplicate where-clauses is not an issue and it can easily happen when elaborating super trait bounds. We now need to make sure we merge all associated type constraints, e.g.

#![allow(unused)]
fn main() {
trait Super {
    type A;
    type B;
}

trait Trait: Super<A = i32> {}
// how to elaborate Trait<B = u32>
}

Or even worse

#![allow(unused)]
fn main() {
trait Super<'a> {
    type A;
    type B;
}

trait Trait<'a>: Super<'a, A = i32> {}
// how to elaborate
// T: Trait<'a> + for<'b> Super<'b, B = u32>
}