# Const trait impls: an overview We have been experimenting with `feature(const_trait_impl)` in the standard library, and here is a summary of the feature as of September 2022. ## Language level So far, the feature only adds four additional elements to the language: `~const` syntax for bounds, `#[const_trait]` for traits, `impl const Trait` syntax for `impl`s, and the `Destruct` marker trait. ### `impl const Trait` This is a fairly straightforward feature. When an `impl` is `const`, we check all method bodies as `const fn`. #### Perennial question: `const impl Trait`? The main rationale behind `impl const Trait` is that `const` looks more like a modifier on the `Trait` and remains consistent with `T: ~const Trait` and future `T: const Trait` bounds. This is the same reason for not having `!impl Trait`s, but `const impl Trait` might also look attractive because `const` is the first modifier on functions. ### `~const` syntax This denotes bounds that should be satisfied in const contexts. It could be satisfied by a concrete impl or via caller bounds like so: ```rust #![feature(const_trait_impl)] #[const_trait] pub trait Tr {} impl const Tr for () {} const fn call_me<T: ~const Tr>() {} const fn foo<T: ~const Tr>() { call_me::<()>(); // concrete type having `impl const` call_me::<T>(); // type satisfied via caller bounds } ``` `~const` is only allowed in bounds where it is a const context, although it doesn't have a precise definition. This is tracked on [#102497](https://github.com/rust-lang/rust/issues/102497). `~const` is allowed in super trait definitions, with relevant tests at [`super-traits.rs`](https://github.com/fee1-dead-contrib/rust/blob/master/src/test/ui/rfc-2632-const-trait-impl/super-traits.rs), [`super-traits-fail.rs`](https://github.com/fee1-dead-contrib/rust/blob/master/src/test/ui/rfc-2632-const-trait-impl/super-traits-fail.rs), and [`super-traits-fail-2.rs`](https://github.com/fee1-dead-contrib/rust/blob/master/src/test/ui/rfc-2632-const-trait-impl/super-traits-fail-2.rs). `~const` is also allowed on associated types in traits, but it was made usage possible in const contexts just recently. [#101989](https://github.com/rust-lang/rust/pull/101989) ### History Before `~const` was [introduced](https://github.com/rust-lang/rust/pull/88328), the model was to infer everything to have a `~const` bound (so `T: Trait` means a const bound in `const fn`), and that `T: ?const Tr` bound is an opt-out for the bound that is satisfiable with non-const implementations. The problem with this model is that 1. the implementation allowed bypassing the restriction. [#90912](https://github.com/rust-lang/rust/issues/90912) 2. Having a `?const` opt-out makes it inconsistent with `const F: fn()` pointers as well as `dyn`. This usage is stable and if they were to be consistent they should be changed to `const F: ?const fn() = ..`. This has been discussed at length on [zulip](https://rust-lang.zulipchat.com/#narrow/stream/146212-t-compiler.2Fconst-eval/topic/const_trait_impl's.20syntax) and [on Internals](https://internals.rust-lang.org/t/pre-rfc-revamped-const-trait-impl-aka-rfc-2632/15192). #### Unresolved Question: Edition bump? Even though `?const` makes it inconsistent for function pointers and dyn types in consts, it was also suggested that a transition to `?const` opt-out everything could be done in an edition bump. Since `const F: fn(..) -> .. = ..` is everywhere, I would expect this to be very inconvenient to migrate. #### Unresolved Question: Final syntax? The tilde `~` sigil was removed from years ago, where it previously meant a `Box<>`ed type. A lot of people are surprised by the syntax and have asked questions on different platforms many times. The current syntax of `~const`, additionally, is inconsistent with `const` definitions. With the feature of `keyword_generics`, it might look like below instead (`~const` as bounded with constness effect, `const` as always const): ```rust ~const fn foo<T: ~const Trait>() {} const fn i_am_always_const() {} impl const Trait for AlwaysConst {} impl ~const Trait for BoundedConst where (): ~const Trait {} ``` Or we could drop the `~` for everything and it becomes this: ```rust const fn foo<T: const Trait>() {} const fn i_am_always_const() {} impl const Trait for AlwaysConst {} impl const Trait for BoundedConst where (): const Trait {} ``` However, we might want to come up with syntax for "always const" bounds such that the following snippet would work as expected with const generics: ```rust fn foo<T: const!! Trait>() -> [u8; T::foo()] { /* ... */ } ``` We could also make the "effect" part explicit, which makes way for "always const" bounds: ```rust // parameterized constness const<C> fn foo<T: const<C> Trait>() {} // always const const fn bar<T: const Trait>() {} ``` But the problem is, no matter what syntax we change this to, it might just end up being something that is taught, instead of something that can be approached and understood intuitively. ### `#[const_trait]` attribute The `#[const_trait]` attribute can only be applied to traits, which will cause all method bodies to be checked as `const fn`, and cause them to implicitly get `where Self: ~const Trait` bounds. All `impl const`s must be for traits that are marked with `#[const_trait]`. #### History This attribute was previously `#[default_method_body_is_const]`, which annotated individual trait methods that have default method bodies. It was [switched](https://github.com/rust-lang/rust/pull/96964) to `#[const_trait]` because of the following reasons: * Permitting anyone to implement `const Trait` without libraries explicitly opting-in to allow `const` implementations presents a semver hazard, where new default bodies without `default_method_body_is_const` constitutes a breaking change. * There is no good syntax for having this on trait methods. `const fn` in traits might take on the meaning of "always `const`" for downstream implementations, which might be desirable. * For traits allowing `const` implementations, they usually have more default bodies that are `const` than those that are non-const. #### Unresolved Question: Opt-out of const default bodies? Traits might want to opt-out of const default bodies and require downstream users to write const implementations when writing an `impl const`. This is still a breaking change, and might not have an actual use. But if this feature ends up getting added, we need to come up with a syntax for this as well. (or just use an attribute) #### Unresolved Question: Syntax? `#[const_trait]` is currently deemed a placeholder. It would be desirable if it was part of the syntax, e.g. `const trait Trait {}`. However, this example's syntax might imply that all implementations must be `const` as well. ### `Destruct` marker trait The `Destruct` marker trait cannot be implemented manually and should only be used through `~const Destruct` bounds. The bound scans the fields of the type provided and ensure that no non-const drop glue get through the trait system. More specifically, a type satisfies `~const Destruct` if either of these are true: * The current context is non-const (type is always droppable in non-const contexts) * The type is required by a where clause or generic bound. `T: ~const Destruct` provided * The type implements `Copy` * The type's fields all satisfy `~const Destruct` * The type is a primitive type, where we know that it can be trivially dropped #### History `T: ~const Destruct` used to be `T: ~const Drop`, but due to inconsistencies with the `T: Drop` bound and complexities with interactions with the cache system of trait selections, it was [renamed](https://github.com/rust-lang/rust/pull/94901) into `Destruct`. In [#102225](https://github.com/rust-lang/rust/pull/102225), we found that increasingly complex generic functions require increasingly more `~const Destruct` bounds on generic parameters. #### Unresolved Question: Naming? In our documentation we use `drop` for running a type's destructor. `Destruct` is never used as a verb. Should we reconsider its naming? #### Unresolved Question: Infer everything? It seems complicated and undesirable if we were to infer the requirements of a generic function for being able to drop things, as changes to the function body opaquely influences the function signature and may or may not constitute a breaking change. However, this eliminates the need for `~const Destruct` completely. #### Unresolved Question: Shorthand? I recently came up with a syntax for simplifying `~const Destruct`, where bounds are written as `T: ~` but this might be harder to teach (and harder to learn) whereas the former's meaning can be inferred. #### Unresolved Question: Opt-out? Since there is a lot of these bounds, could we just make it so that users only define types that will not be dropped? This could have problems where it is unclear whether the definition of the opt-out would work all the time (particularly with super traits, associated type's bounds, and stuff where the type is used by downstream). It also imposes more restrictions on perfectly stable `const fn`s like this: ```rust const fn a<A>() {} ``` #### Roadmap: `!Destruct` In the future, we might have undroppable types that are checked such that it is never dropped. Currently, people use `panic!` to indicate this, but it could be useful as a language feature to ensure this at compile time. ## Interaction with standard library Many issues were found while experimenting within the standard library. [most of my merged pull requests](https://github.com/rust-lang/rust/pulls?q=is%3Apr+author%3Afee1-dead+is%3Aclosed+is%3Amerged+) containing bugfixes were found while using this feature in the standard library. The feature took 1.3 years of continuous development, mostly only through working with the standard library. This only changed recently were the feature was picked up by more users and people started contributing `const` `impl`s in the standard library (most probably for use within their own applications). The standard library is useful in detecting edge cases because it uses generics extensively within the trait system (expecially around the `Iterator` trait) which can help catch bugs from components that had the most bugs: trait selection. Without the `Iterator` trait being tested with `const` `impl`s, it is impossible to test `for` loop desugarings. The same goes for the `Try` trait and `?`-desugaring.