diff --git a/src/doc/rustc-dev-guide/src/solve/candidate-preference.md b/src/doc/rustc-dev-guide/src/solve/candidate-preference.md index 22ec3162b73f..56fc865020a4 100644 --- a/src/doc/rustc-dev-guide/src/solve/candidate-preference.md +++ b/src/doc/rustc-dev-guide/src/solve/candidate-preference.md @@ -1,16 +1,23 @@ # Candidate preference -There are multiple ways to prove `Trait` and `NormalizesTo` goals. Each such option is called a [`Candidate`]. If there are multiple applicable candidates, we prefer some candidates over others. We store the relevant information in their [`CandidateSource`]. +There are multiple ways to prove `Trait` and `NormalizesTo` goals. +Each such option is called a [`Candidate`]. +If there are multiple applicable candidates, we prefer some candidates over others. +We store the relevant information in their [`CandidateSource`]. -This preference may result in incorrect inference or region constraints and would therefore be unsound during coherence. Because of this, we simply try to merge all candidates in coherence. +This preference may result in incorrect inference or region constraints and would therefore be unsound during coherence. +Because of this, we simply try to merge all candidates in coherence. ## `Trait` goals -Trait goals merge their applicable candidates in [`fn merge_trait_candidates`]. This document provides additional details and references to explain *why* we've got the current preference rules. +Trait goals merge their applicable candidates in [`fn merge_trait_candidates`]. +This document provides additional details and references to explain *why* we've got the current preference rules. ### `CandidateSource::BuiltinImpl(BuiltinImplSource::Trivial))` -Trivial builtin impls are builtin impls which are known to be always applicable for well-formed types. This means that if one exists, using another candidate should never have fewer constraints. We currently only consider `Sized` - and `MetaSized` - impls to be trivial. +Trivial builtin impls are builtin impls which are known to be always applicable for well-formed types. +This means that if one exists, using another candidate should never have fewer constraints. +We currently only consider `Sized` - and `MetaSized` - impls to be trivial. This is necessary to prevent a lifetime error for the following pattern @@ -50,7 +57,8 @@ where ### `CandidateSource::ParamEnv` Once there's at least one *non-global* `ParamEnv` candidate, we prefer *all* `ParamEnv` candidates over other candidate kinds. -A where-bound is global if it is not higher-ranked and doesn't contain any generic parameters. It may contain `'static`. +A where-bound is global if it is not higher-ranked and doesn't contain any generic parameters. +It may contain `'static`. We try to apply where-bounds over other candidates as users tends to have the most control over them, so they can most easily adjust them in case our candidate preference is incorrect. @@ -68,7 +76,8 @@ fn foo<'a, T: Trait<'a>>() { } ``` -We also need this as shadowed impls can result in currently ambiguous solver cycles: [trait-system-refactor-initiative#76]. Without preference we'd be forced to fail with ambiguity +We also need this as shadowed impls can result in currently ambiguous solver cycles: [trait-system-refactor-initiative#76]. +Without preference we'd be forced to fail with ambiguity errors if the where-bound results in region constraints to avoid incompleteness. ```rust trait Super { @@ -94,7 +103,9 @@ fn overflow() { } ``` -This preference causes a lot of issues. See [#24066]. Most of the +This preference causes a lot of issues. +See [#24066]. +Most of the issues are caused by preferring where-bounds over impls even if the where-bound guides type inference: ```rust trait Trait { @@ -167,7 +178,10 @@ where #### Why no preference for global where-bounds -Global where-bounds are either fully implied by an impl or unsatisfiable. If they are unsatisfiable, we don't really care what happens. If a where-bound is fully implied then using the impl to prove the trait goal cannot result in additional constraints. For trait goals this is only useful for where-bounds which use `'static`: +Global where-bounds are either fully implied by an impl or unsatisfiable. +If they are unsatisfiable, we don't really care what happens. +If a where-bound is fully implied then using the impl to prove the trait goal cannot result in additional constraints. +For trait goals this is only useful for where-bounds which use `'static`: ```rust trait A { @@ -181,13 +195,15 @@ where x.test(); } ``` -More importantly, by using impls here we prevent global where-bounds from shadowing impls when normalizing associated types. There are no known issues from preferring impls over global where-bounds. +More importantly, by using impls here we prevent global where-bounds from shadowing impls when normalizing associated types. +There are no known issues from preferring impls over global where-bounds. #### Why still consider global where-bounds Given that we just use impls even if there exists a global where-bounds, you may ask why we don't just ignore these global where-bounds entirely: we use them to weaken the inference guidance from non-global where-bounds. -Without a global where-bound, we currently prefer non-global where bounds even though there would be an applicable impl as well. By adding a non-global where-bound, this unnecessary inference guidance is disabled, allowing the following to compile: +Without a global where-bound, we currently prefer non-global where bounds even though there would be an applicable impl as well. +By adding a non-global where-bound, this unnecessary inference guidance is disabled, allowing the following to compile: ```rust fn check(color: Color) where @@ -209,7 +225,9 @@ impl From for f32 { ### `CandidateSource::AliasBound` -We prefer alias-bound candidates over impls. We currently use this preference to guide type inference, causing the following to compile. I personally don't think this preference is desirable 🤷 +We prefer alias-bound candidates over impls. +We currently use this preference to guide type inference, causing the following to compile. +I personally don't think this preference is desirable 🤷 ```rust pub trait Dyn { type Word: Into; @@ -254,7 +272,9 @@ fn foo<'a, T: Trait<'a>>() { ### `CandidateSource::BuiltinImpl(BuiltinImplSource::Object(_))` -We prefer builtin trait object impls over user-written impls. This is **unsound** and should be remoed in the future. See [#57893](https://github.com/rust-lang/rust/issues/57893) and [#141347](https://github.com/rust-lang/rust/pull/141347) for more details. +We prefer builtin trait object impls over user-written impls. +This is **unsound** and should be remoed in the future. +See [#57893](https://github.com/rust-lang/rust/issues/57893) and [#141347](https://github.com/rust-lang/rust/pull/141347) for more details. ## `NormalizesTo` goals @@ -336,7 +356,7 @@ Even if the trait goal was proven via an impl, we still prefer `ParamEnv` candid #### We prefer "orphaned" where-bounds We add "orphaned" `Projection` clauses into the `ParamEnv` when normalizing item bounds of GATs and RPITIT in `fn check_type_bounds`. -We need to prefer these `ParamEnv` candidates over impls and other where-bounds. +We need to prefer these `ParamEnv` candidates over impls and other where-bounds. ```rust #![feature(associated_type_defaults)] trait Foo { @@ -355,7 +375,8 @@ I don't fully understand the cases where this preference is actually necessary a #### We prefer global where-bounds over impls -This is necessary for the following to compile. I don't know whether anything relies on it in practice 🤷 +This is necessary for the following to compile. +I don't know whether anything relies on it in practice 🤷 ```rust trait Id { type This; @@ -423,7 +444,8 @@ where #### RPITIT `type_of` cycles -We currently have to avoid impl candidates if there are where-bounds to avoid query cycles for RPITIT, see [#139762]. It feels desirable to me to stop relying on auto-trait leakage of during RPITIT computation to remove this issue, see [#139788]. +We currently have to avoid impl candidates if there are where-bounds to avoid query cycles for RPITIT, see [#139762]. +It feels desirable to me to stop relying on auto-trait leakage of during RPITIT computation to remove this issue, see [#139788]. ```rust use std::future::Future; @@ -457,8 +479,8 @@ where #### Trait definition cannot use associated types from always applicable impls The `T: Trait` assumption in the trait definition prevents it from normalizing -`::Assoc` to `T` by using the blanket impl. This feels like a somewhat -desirable constraint, if not incredibly so. +`::Assoc` to `T` by using the blanket impl. +This feels like a somewhat desirable constraint, if not incredibly so. ```rust trait Eq {} @@ -486,4 +508,4 @@ impl Trait for T { [#24066]: https://github.com/rust-lang/rust/issues/24066 [#133044]: https://github.com/rust-lang/rust/issues/133044 [#139762]: https://github.com/rust-lang/rust/pull/139762 -[#139788]: https://github.com/rust-lang/rust/issues/139788 \ No newline at end of file +[#139788]: https://github.com/rust-lang/rust/issues/139788