## Effects Summary 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: ```rust #[const_trait] trait Foo { type Assoc; fn method(); } impl const Foo for () { type Assoc = (); fn method() {} } ``` Before the new proposed desugaring, this currently desugars into: ```rust trait Foo<const RUNTIME: bool> { type Assoc; fn method(); } impl<const RUNTIME: bool> Foo<RUNTIME> for () { type Assoc = (); fn method() {} } ``` 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](https://rust-lang.zulipchat.com/#narrow/stream/419616-t-compiler.2Fproject-const-traits/topic/projections.20on.20.28~.29const.20Trait.20.26.20.28~.29const.20assoc.20ty.20bounds). 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? ```rust #[const_trait] trait Foo { type Assoc; fn method(); } impl const Foo for () { type Assoc = (); fn method() {} } ``` Let's revisit this example. Under the new desugaring, this would result in: ```rust trait Foo { type Effects; type Assoc; fn method<const RUNTIME: bool>() where Self::Effects: Compat<RUNTIME>; } impl Foo for () { type Effects = Maybe; type Assoc = (); fn method<const RUNTIME: bool>() where Self::Effects: Compat<RUNTIME> {} } ``` 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) ```rust #[lang = "EffectsCompat"] pub trait Compat<#[rustc_runtime] const RUNTIME: bool> {} impl Compat<false> for NoRuntime {} impl Compat<true> for Runtime {} impl<#[rustc_runtime] const RUNTIME: bool> Compat<RUNTIME> for Maybe {} ``` 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: * Bounds on a trait/impl method * Bounds on an associated type in a trait * Super traits * Where clauses on trait/impl We'll take a look at how these can be desugared. ## bounds on method What happens to `~const` bounds on methods? Take the example ```rust #[const_trait] trait Foo { fn method<T: ~const Bar>(); } ``` We'd desugar it as ```rust trait Foo { type Effects; fn method<const RUNTIME: bool, T: Bar>() where Self::Effects: Compat<RUNTIME>, <T as Bar>::Effects: Compat<RUNTIME>; } ``` 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. ## where clauses, the `TyCompat` trait and the `Min` trait Suppose we have a where clause on a trait. ```rust #[const_trait] trait Foo<T> where T: ~const Bar {} ``` 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: ```rust trait Foo<T> where T: Bar { type Effects: TyCompat<<(<T as Bar>::Effects,) as Min>::Output>; } ``` Where we can define TyCompat as ```rust trait TyCompat<Super> {} impl TyCompat<Maybe> for NoRuntime {} impl TyCompat<Maybe> for Runtime {} impl<T> TyCompat<T> for T {} ``` Where clauses on impls will cause the impl's effect to be the minimum of all effects. ```rust impl<T> const Foo<T> for Uwu<T> where T: ~const Bar + ~const Baz {} // desugars into impl<T> Foo<T> for Uwu<T> where T: Bar { type Effects = <(<T as Bar>::Effects, <T as Baz>::Effects) as Min>::Output; } ``` 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`. ## assoc type bounds in traits Bounds on associated types need to relate to the effects on the current trait. If we had ```rust #[const_trait] trait Foo { type Assoc: ~const Bar; } ``` 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>>`. ## super traits ```rust #[const_trait] trait Bar {} #[const_trait] trait Foo: ~const Bar {} ``` would desugar into ```rust trait Bar { type Effects; } trait Foo: Bar { type Effects: TyCompat<<(<Self as Bar>::Effects,) as Min>::Output>; } ```