|
| 1 | +## Significant changes and quirks |
| 2 | + |
| 3 | +While some of the items below are already mentioned separately, this page tracks the |
| 4 | +main changes from the old trait system implementation. This also mentions some ways |
| 5 | +in which the solver significantly diverges from an idealized implementation. This |
| 6 | +document simplifies and ignores edge cases. It is recommended to add an implicit |
| 7 | +"mostly" to each statement. |
| 8 | + |
| 9 | +### Canonicalization |
| 10 | + |
| 11 | +The new solver uses [canonicalization] when evaluating nested goals. In case there |
| 12 | +are possibly multiple candidates, each candidate is eagerly canonicalized. We then |
| 13 | +attempt to merge their canonical responses. This differs from the old implementation |
| 14 | +which does not use canonicalization inside of the trait system. |
| 15 | + |
| 16 | +This has a some major impacts on the design of both solvers. Without using |
| 17 | +canonicalization to stash the constraints of candidates, candidate selection has |
| 18 | +to discard the constraints of each candidate, only applying the constraints by |
| 19 | +reevaluating the candidate after it has been selected: [source][evaluate_stack]. |
| 20 | +Without canonicalization it is also not possible to cache the inference constraints |
| 21 | +from evaluating a goal. This causes the old implementation to have two systems: |
| 22 | +*evaluate* and *fulfill*. *Evaluation* is cached, does not apply inference constraints |
| 23 | +and is used when selecting candidates. *Fulfillment* applies inference and region |
| 24 | +constraints is not cached and applies inference constraints. |
| 25 | + |
| 26 | +By using canonicalization, the new implementation is able to merge *evaluation* and |
| 27 | +*fulfillment*, avoiding complexity and subtle differences in behavior. It greatly |
| 28 | +simplifies caching and prevents accidentally relying on untracked information. |
| 29 | +It allows us to avoid reevaluating candidates after selection and enables us to merge |
| 30 | +the responses of multiple candidates. However, canonicalizing goals during evaluation |
| 31 | +forces the new implementation to use a fixpoint algorithm when encountering cycles |
| 32 | +during trait solving: [source][cycle-fixpoint]. |
| 33 | + |
| 34 | +[canoncalization]: ./canonicalization.md |
| 35 | +[evaluate_stack]: https://github.com/rust-lang/rust/blob/47dd709bedda8127e8daec33327e0a9d0cdae845/compiler/rustc_trait_selection/src/traits/select/mod.rs#L1232-L1237 |
| 36 | +[cycle-fixpoint]: https://github.com/rust-lang/rust/blob/df8ac8f1d74cffb96a93ae702d16e224f5b9ee8c/compiler/rustc_trait_selection/src/solve/search_graph.rs#L382-L387 |
| 37 | + |
| 38 | +### Deferred alias equality |
| 39 | + |
| 40 | +The new implementation emits `AliasRelate` goals when relating aliases while the |
| 41 | +old implementation structurally relates the aliases instead. This enables the |
| 42 | +new solver to stall equality until it is able to normalize the related aliases. |
| 43 | + |
| 44 | +The behavior of the old solver is incomplete and relies on eager normalization |
| 45 | +which replaces ambiguous aliases with inference variables. As this is not |
| 46 | +not possible for aliases containing bound variables, the old implementation does |
| 47 | +nto handle aliases inside of binders correctly, e.g. [#102048]. See the chapter on |
| 48 | +[normalization] for more details. |
| 49 | + |
| 50 | +[#102048]: https://github.com/rust-lang/rust/issues/102048 |
| 51 | + |
| 52 | +### Eagerly evaluating nested goals |
| 53 | + |
| 54 | +The new implementation eagerly handles nested goals instead of returning |
| 55 | +them to the caller. The old implementation does both. In evaluation nested |
| 56 | +goals [are eagerly handled][eval-nested], while fulfillment simply |
| 57 | +[returns them for later processing][fulfill-nested]. |
| 58 | + |
| 59 | +As the new implementation has to be able to eagerly handle nested goals for |
| 60 | +candidate selection, always doing so reduces complexity. It may also enable |
| 61 | +us to merge more candidates in the future. |
| 62 | + |
| 63 | +[eval-nested]: https://github.com/rust-lang/rust/blob/master/compiler/rustc_trait_selection/src/traits/select/mod.rs#L1271-L1277 |
| 64 | +[fulfill-nested]: https://github.com/rust-lang/rust/blob/df8ac8f1d74cffb96a93ae702d16e224f5b9ee8c/compiler/rustc_trait_selection/src/traits/fulfill.rs#L708-L712 |
| 65 | + |
| 66 | +### Nested goals are evaluated until reaching a fixpoint |
| 67 | + |
| 68 | +The new implementation always evaluates goals in a loop until reachin a fixpoint. |
| 69 | +The old implementation only does so in *fulfillment*, but not in *evaluation*. |
| 70 | +Always doing so strengthens inference and is reduces the order dependence of |
| 71 | +the trait solver. See [trait-system-refactor-initiative#102]. |
| 72 | + |
| 73 | +[trait-system-refactor-initiative#102]: https://github.com/rust-lang/trait-system-refactor-initiative/issues/102 |
| 74 | + |
| 75 | +### Proof trees and providing diagnostics information |
| 76 | + |
| 77 | +The new implementation does not track diagnostics information directly, |
| 78 | +instead providing [proof trees][trees] which are used to lazily compute the |
| 79 | +relevant information. This is not yet fully fleshed out and somewhat hacky. |
| 80 | +The goal is to avoid tracking this information in the happy path to improve |
| 81 | +performance and to avoid accidentally relying on diagnostics data for behavior. |
| 82 | + |
| 83 | +[trees]: ./proof-trees.md |
| 84 | + |
| 85 | +## Major quirks of the new implementation |
| 86 | + |
| 87 | +### Hiding impls if there are any env candidates |
| 88 | + |
| 89 | +If there is at least one `ParamEnv` or `AliasBound` candidate to prove |
| 90 | +some `Trait` goal, we discard all impl candidates for both `Trait` and |
| 91 | +`Projection` goals: [source][discard-from-env]. This prevents users from |
| 92 | +using an impl which is entirely covered by a `where`-bound, matching the |
| 93 | +behavior of the old implementation and avoiding some weird errors, |
| 94 | +e.g. [trait-system-refactor-initiative#76]. |
| 95 | + |
| 96 | +[discard-from-env]: https://github.com/rust-lang/rust/blob/03994e498df79aa1f97f7bbcfd52d57c8e865049/compiler/rustc_trait_selection/src/solve/assembly/mod.rs#L785-L789 |
| 97 | +[trait-system-refactor-initiative#76]: https://github.com/rust-lang/trait-system-refactor-initiative/issues/76 |
| 98 | + |
| 99 | +### `NormalizesTo` goals are a function |
| 100 | + |
| 101 | +See the [normalizaton] chapter. We replace the expected term with an unconstrained |
| 102 | +inference variable before computing `NormalizesTo` goals to prevent it from affecting |
| 103 | +normalization. This means that `NormalizesTo` goals are handled somewhat differently |
| 104 | +from all other goal kinds and need some additional solver support. Most notably, |
| 105 | +their ambiguous nested goals are returned to the caller which then evaluates them. |
| 106 | +See [#122687] for more details. |
| 107 | + |
| 108 | +[#122687]: https://github.com/rust-lang/rust/pull/122687 |
| 109 | +[normalization]: ./normalization.md |
0 commit comments