diff --git a/text/0000-const-default.md b/text/0000-const-default.md new file mode 100644 index 00000000000..76f9cbd2d57 --- /dev/null +++ b/text/0000-const-default.md @@ -0,0 +1,299 @@ +- Feature Name: const_default +- Start Date: 2017-10-17 +- RFC PR: (leave this empty) +- Rust Issue: (leave this empty) + +# Summary +[summary]: #summary + +1. Adds the trait `ConstDefault` to libcore defined as: + +```rust +pub trait ConstDefault: Default { const DEFAULT: Self; } +``` + +2. Adds impls for all types which are `Default` and where the returned value in +`default` can be `const`. This includes impls for tuples where all factors are +`ConstDefault`. + +3. Enables deriving of `ConstDefault` for structs iff all fields are also +`ConstDefault`. When `Default` and `ConstDefault` are derived together, +`fn default()` is derived as `= Self::DEFAULT`. + +# Motivation +[motivation]: #motivation + +The `Default` trait gives a lot of expressive power to the developer. However, +`Default` makes no compile-time guarantees about the cheapness of using `default` +for a particular type. It can be useful to statically ensure that any default +value of a type does not have large unforseen runtime costs. + +With a default `const` value for a type, the developer can be more certain +(large stack allocated arrays may still be costly) that constructing the default +value is cheap. + +An additional motivation is having a way of getting a default constant +value when dealing with `const fn` as well as const generics. For such +constexpr + generics to work well, more traits may however be required in the future. + +# Guide-level explanation +[guide-level-explanation]: #guide-level-explanation + +## The trait + +The following trait: + +```rust +pub trait ConstDefault: Default { + const DEFAULT: Self; +} +``` + +is added to `core::default` and re-exported in `std::default`. + +This trait should be directly used if: ++ you want to be sure that the default value does not depend on the runtime. ++ you want to use the default value in a `const fn`. + +You may also, at your leisure, continue using `Default` for type `T` which +will yield `::DEFAULT`. This is especially true if you are +a newcomer to the language as `const` may be considered an advanced topic. + +## `impls` for standard library + +Then, `impl`s are added to the types that exist in the standard library which +are `Default` and for which the default value can be `const`. + +For the numeric types, with `usize` as an example, the `impl`s like the +following are added: + +```rust +impl ConstDefault for usize { + const DEFAULT: Self = 0; +} +``` + +Another example is for `()`, which less useful, but nonetheless informative: +```rust +impl ConstDefault for () { + const DEFAULT: Self = (); +} +``` + +Another, more interesting, case is for `Option`: +```rust +impl ConstDefault for Option { + const DEFAULT: Self = None; +} +``` + +Equally interesting is the case for tuples: + +```rust +impl ConstDefault for (T0, T1) +where + T0: ConstDefault, + T1: ConstDefault, +{ + const DEFAULT: Self = (T0::DEFAULT, T1::DEFAULT); +} + +impl ConstDefault for (T0, T1, T2) +where + T0: ConstDefault, + T1: ConstDefault, + T2: ConstDefault, +{ + const DEFAULT: Self = (T0::DEFAULT, T1::DEFAULT, T2::DEFAULT); +} + +// and so on.. +``` + +For arrays, impls like the following can be added: + +```rust +impl ConstDefault for [T; 2] { + const DEFAULT: Self = [T::DEFAULT, T::DEFAULT]; +} +``` + +## Deriving + +Just as you can `#[derive(Default)]`, so will you be able to +`#[derive(ConstDefault)]` iff all of the type's fields implement `ConstDefault`. +When derived, the type will use `::DEFAULT` where +`Field` is each field's type. + +Notably also, since `ConstDefault` implies `Default`, it is a logic error on the +part of the programmer for `Self::default() != Self::DEFAULT`, wherefore +the compiler, when seeing `Default` and `ConstDefault` being derived together +will emit: + +```rust +impl Default for DerivedForType { + fn default() -> Self { Self::DEFAULT } +} +``` + +Assuming a struct with type parameters like `struct Foo(A);`, +the compiler derives: + +```rust +impl Default for Foo { + default fn default() -> Self { Foo(A::default()) } +} + +impl Default for Foo { + fn default() -> Self { Self::DEFAULT } +} + +impl ConstDefault for Foo { + const DEFAULT: Self = Foo(A::DEFAULT); +} +``` + +# Reference-level explanation +[reference-level-explanation]: #reference-level-explanation + +## The trait + +The following is added to `core::default` (libcore) as well as `std::default` +(stdlib, reexported): + +```rust +pub trait ConstDefault: Default { + const DEFAULT: Self; +} +``` + +## `impls` for standard library + +Impls are added for all types which are `Default` and where the returned +value in `::default()` can be `const`. + +Many of these `Default` `impl`s are generated by macros. Such macros are changed +to generate `ConstDefault` `impl`s as well as a manually encoded "blanket" +`impl` instead. + +An example of how such a changed macro might look like is: + +```rust +macro_rules! blanket_impl { + ($type: ty) => { + impl Default for $type { + fn default() -> Self { Self::DEFAULT } + } + }; +} + +macro_rules! impl_cd_zero { + ($($type: ty),+) => { + $( + blanket_impl!($type); + impl ConstDefault for $type { + const DEFAULT: Self = 0; + } + )+ + }; +} + +impl_cd_zero!(u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize); +``` + +These macros are internal to the standard library which is free to achieve +equivalent results by other means. + +[const generics]: https://github.com/rust-lang/rfcs/blob/master/text/2000-const-generics.md +[const repeat expressions]: https://github.com/Centril/rfcs/blob/rfc/const-repeat-expr/text/0000-const-repeat-expr.md + +Impls are also generated by macro for tuples and arrays. +If implemented [const generics] and [const repeat expressions] can be used to +implement the trait for arrays of arbitrary size, otherwise, impls can be +generated by macro for arrays up to a reasonable size. + +## Deriving + +The mechanism and rules used for deriving `Default` are reused for `ConstDefault`. +They are however altered to produce a `const` item in the trait instead of a +function, and instead of a trait function call, the following is used for a +factor `Field` of a product type (tuples structs, normal structs - including +unit structs): `::DEFAULT`. + +The compiler also handles deriving `ConstDefault` in conjunction with `Default` +specially by emitting an `impl Default` that uses `Self::DEFAULT`. The compiler +is however not obliged to do this, since `Self::default() != Self::DEFAULT` must +always be upheld by the programmer. To not uphold this is a logic error. +Special-casing might however have some compile-time performance benefits. + +## In relation to "Default Fields" + +[RFC 1806]: https://github.com/rust-lang/rfcs/pull/1806 + +The currently postponed [RFC 1806], which deals with struct default field values, +allows the user to assign default values from `const` expressions to fields when +defining a `struct` as in the following example: + +```rust +struct Foo { + a: &'static str, + b: bool = true, + c: i32, +} +``` + +That RFC argues that an alternative to the `const` requirement is to allow the +use of `Default::default()` instead of just `const` expressions. However, +since `Default` may incur non-trival runtime costs which are un-predictable, +this is not the main recommendation of the RFC. As `::DEFAULT` +is const, this RFC is fully compatible with that RFC. + +[RFC 1806] further mandates that when deriving `Default`, supplied field defaults +are used instead of the field type's `Default` impl. If RFC 1806 is added to this +language, for the sake of consistency the same logic should also apply to +`ConstDefault`. + +# Drawbacks +[drawbacks]: #drawbacks + +As always, adding this comes with the cost incurred of adding a trait and in +particular all the impls that come with it in the standard library. + +# Rationale and alternatives +[alternatives]: #alternatives + +This design may in fact not be optimal. A more optimal solution may be to +add a `const` modifier on the bound of a trait which "magically" causes all +`fn`s in it to be considered `const fn` if possible. This bound may look like: +`T: const Default`. If there are any such implemented trait `fn`s for a given +type which can not also be considered `const fn`, then the bound will be +considered not fulfilled for the given type under impl. + +The `T: const Default` approach may be considered heavy handed. In this case +it may be considered a bludgeon while the following approach is a metaphorical +scalpel: `::method: ConstFn`. With this bound, the compiler is told +that `fn method` of `Trait` for `T` may be considered a `const fn`. + +While the design offered by this RFC is not optimal compared to the two latter, +it is implementable today and is not blocked by: a) adding `const fn` to traits, +b) adding a `const` modifier to bounds. Such a proposal, while useful, is only +realizable far into the future. In the case of the last alternative, +Rust must be able to encode bounds for trait `fn`s as well as adding marker trait +which constrains the `fn`s to be const. This is most likely even more futuristic. + +This RFC advocates that the more optimal alteratives are sufficiently far into +the future that the best course of action is to add `ConstDefault` now and then +deprecate it when **and if** any of the more optimal alternatives are added. + +[RFC 1520]: https://github.com/rust-lang/rfcs/pull/1520 + +The `ConstDefault` proposed by this RFC was also independently discussed and +derived in the now closed [RFC 1520] as what could be achieved with generic consts. +The trait was not the actual suggestion of the RFC but rather discussed in passing. +However, the fact that the same identical trait was developed independently gives +greater confidence in its design. + +# Unresolved questions +[unresolved]: #unresolved-questions + +None, as of yet. \ No newline at end of file