quadrant (const)
TL;DR
To declare a functon with a "conditionally const" bound, you write (const) Default
#![allow(unused)] fn main() { const fn get<T: (const) Default> {...} }
To declare a functon with an "always const" bound, you write const Default
#![allow(unused)] fn main() { const fn get<T: const Default> { const { T::default() } } }
To declare a trait with a method that is "conditionally const" depending on the impl:
#![allow(unused)] fn main() { trait Default { (const) fn default() -> Self; } }
To implement the trait with an impl that is always const:
#![allow(unused)] fn main() { impl Default for i32 { const fn default() -> i32 { 0 } } }
To implement the trait with an impl that is never const:
#![allow(unused)] fn main() { impl<T> Default for Box<T> where T: Default, { fn default() -> Self { Box::new(T::default()) } } }
To implement the trait with a "conditionally const" impl:
#![allow(unused)] fn main() { impl<T> Default for [T; 1] where T: (const) Default, { (const) fn default() -> Self { // (*) [T::default()] } // (*) it is possible this should be `const`, we can decide, // I'm not sure yet which I like better. } }
The model also supports
- "always-const" methods
- "always-maybe-const" methods
see below.
Design axioms
- The step from a monomorphic
const fn
to one with a generic bound should be minimal - If we do a general syntax, it should cover all the "quadrants"
- Fn items and trait items should mean same thing
- You should be able to declare a "maybe const" function without understanding effects
- Twiddle const is where Rust jumps the shark
Due to satisfying the last axiom, this design is compatible with either
or
const
andasync
should "feel the same"- We will want to have more effects than
const
- We will want a "lower floor" than
const
- Traits in libstd that becomes a "const trait" will want to be an "async trait" too
Default trait and impl
Equivalent of default trait and impl:
#![allow(unused)] fn main() { trait Default { // Declare the item as conditionally const (depending on the impl): (const) fn default() -> Self; } struct Wrapper<T> { value: T } impl<T> Default for Wrapper<T> where T: (const) Default, { // When implementing a conditionally const method, you write `(const)` // to indicate that its effects may depend on the impl effect // (just as in the trait). The impl effect here includes the // `T: (const) Default` from the impl header. // // Variation: We could also have you write `const fn default() -> Self` // here. See discussion below. (const) fn default() -> Self { Wrapper { value: T::default() } } } const fn get_me<T>(x: Option<Wrapper<T>>) -> Wrapper<T> where T: (const) Default, { match x { None => <Wrapper<T>>::default(), Some(v) => v, } } }
Desugaring walkthrough
#![allow(unused)] fn main() { trait Default { // Declare the item as conditionally const (depending on the impl): (const) fn default() -> Self; } }
This is desugared to
#![allow(unused)] fn main() { trait Default { do DefaultEffect; // <-- because of the `(const)` fn default() -> Self do <Self as Default>::DefaultEffect; // <-- because of the `(const)` } }
The function get_me
would be desugared as:
#![allow(unused)] fn main() { fn get_me<T>(x: Option<Wrapper<T>>) -> Wrapper<T> where T: Default, do <T as Default>::Effect, // <-- because of the `T: (const) Default` { match x { None => <Wrapper<T>>::default(), Some(v) => v, } } }
The impl
is desugared as
#![allow(unused)] fn main() { impl<T> Default for Wrapper<T> where T: Default, { do DefaultEffect = ( const, // <-- included because `default` was declared as `(const)` <T as Default>::Effect, // <-- because of `T: (const) Default` from the impl header ); fn default() -> Self do <Self as Default>::DefaultEffect, // <-- because `default` was declared as `(const)` { Wrapper { value: T::default() } } } }
All the things
#![allow(unused)] fn main() { trait ATT { // const in all impls: const fn always_const(&self) -> u32; // const if declared in impl as const (const) fn maybe_const(&self) -> u32; // const if (a) declared in impl as const and (b) methods in `T: ATT` impl are const (const) fn maybe_maybe_const<T: (const) ATT>(&self) -> u32; // includes effects from `T` const fn always_maybe_const<T: (const) ATT>(&self) -> u32; } }