Skip to content

Commit d0132d9

Browse files
committed
add chapter on projection vs trait split
1 parent c48c591 commit d0132d9

2 files changed

Lines changed: 42 additions & 0 deletions

File tree

src/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,7 @@
189189
- [Significant changes and quirks](./solve/significant-changes.md)
190190
- [Sharing the trait solver with rust-analyzer](./solve/sharing-crates-with-rust-analyzer.md)
191191
- [`Unsize` and `CoerceUnsized` traits](./traits/unsize.md)
192+
- [Having separate `Trait` and `Projection` bounds](./traits/separate-projection-bounds.md)
192193
- [Variance](./variance.md)
193194
- [Coherence checking](./coherence.md)
194195
- [HIR Type checking](./hir-typeck/summary.md)
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Having separate `Trait` and `Projection` bounds
2+
3+
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?
4+
5+
The way we prove `Projection` bounds directly relies on proving the corresponding `Trait` bound:
6+
- old solver: https://github.com/rust-lang/rust/blob/461e9738a47e313e4457957fa95ff6a19a4b88d4/compiler/rustc_trait_selection/src/traits/project.rs#L898
7+
- new solver: https://github.com/rust-lang/rust/blob/461e9738a47e313e4457957fa95ff6a19a4b88d4/compiler/rustc_next_trait_solver/src/solve/normalizes_to/mod.rs#L37-L41
8+
9+
We may use a different candidate for norm than for the corresponding trait bound:
10+
- https://rustc-dev-guide.rust-lang.org/solve/candidate-preference.html#we-always-consider-aliasbound-candidates
11+
- https://rustc-dev-guide.rust-lang.org/solve/candidate-preference.html#we-prefer-orphaned-where-bounds
12+
13+
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.
14+
15+
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.
16+
17+
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.
18+
19+
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.
20+
21+
```rust
22+
trait Super {
23+
type A;
24+
type B;
25+
}
26+
27+
trait Trait: Super<A = i32> {}
28+
// how to elaborate Trait<B = u32>
29+
```
30+
Or even worse
31+
```rust
32+
trait Super<'a> {
33+
type A;
34+
type B;
35+
}
36+
37+
trait Trait<'a>: Super<'a, A = i32> {}
38+
// how to elaborate
39+
// T: Trait<'a> + for<'b> Super<'b, B = u32>
40+
```
41+

0 commit comments

Comments
 (0)