The new desugaring focuses entirely on traits and how they are treated with the effects system.
Before we get to the new desugaring, let's first take a look at the old desugaring:
Before the new proposed desugaring, this currently desugars into:
This lead to problems with projections, as it is now possible for <T as Foo<true>>::Assoc != <T as Foo<false>>::Assoc
, and forces us to use syntax like <Type as ~const Foo>::Assoc
.
A discussion of this approach can be found in this Zulip thread. My biggest concern for this would be "how do we teach this", as the requirements for using <T as const Tr>::Assoc
vs <T as ~const Tr>::Assoc
vs <T as Tr>::Assoc
can be unclear/confusing to users.
But note that we would be in this situation only by suggesting that having a #[const_trait]
creates two separate traits for in a const environment vs in a non-const environment. This paradigm would then make associated types ambiguous. So what if we made it a single trait only?
Let's revisit this example. Under the new desugaring, this would result in:
We're implementing one trait instead of two traits, which resolves the issue with projecting associated types.
The effects type could be one out of three types: Maybe
, which means both const and runtime environment allowed, NoRuntime
, which only allows running in const, and Runtime
, which only allows running in runtime.
Each method gets their own RUNTIME
parameter, and we use the where clause to guard against const contexts calling methods on traits only implemented for a runtime context. The Compat
trait is defined as follows: (this should be what you expect)
Now, there are many places a ~const
bound can appear while interacting with the system of encoding effects through an associated type. Here's a list of them:
We'll take a look at how these can be desugared.
What happens to ~const
bounds on methods? Take the example
We'd desugar it as
Thus, when we are calling from a runtime context, T: Bar
must be compatible with calling from runtime, and vice versa when in a const context. The Compat
bound does all the work in checking that effects requirements are satisfied.
TyCompat
trait and the Min
traitSuppose we have a where clause on a trait.
This clearly means that the current effect must be a subset of the effect on T: Bar
. Therefore, we can use a TyCompat
trait (naming bikesheddable) to encode this:
Where we can define TyCompat as
Where clauses on impls will cause the impl's effect to be the minimum of all effects.
The Min
trait represents an operator for intersection, with for example (Maybe, Runtime): Min<Output = Runtime>
, (Maybe, Maybe): Min<Output = Maybe>
, and (Runtime, NoRuntime): !Min
.
Bounds on associated types need to relate to the effects on the current trait. If we had
We would expect Assoc
to fully allow the environment impl Foo for Self
has. That is, if Foo
is implemented with Maybe
(both runtime and const allowed), then Self::Assoc
should also be implemented with Maybe
. If Foo
is implemented with Runtime
or NoRuntime
, then Self::Assoc
should be implemented with the same effect or Maybe
which is more relaxed.
We can encode this with a trait bound, to desugar this into type Assoc: Bar<Effects: TyCompat<Self::Effects>>
.
would desugar into