From 9d24b973364b7f6515f11b1538dbc9ebf1b1922b Mon Sep 17 00:00:00 2001 From: Deadbeef Date: Sun, 27 Sep 2026 15:21:07 -0400 Subject: [PATCH 1/7] add 0000-min-const-traits.md --- text/0000-min-const-traits.md | 504 ++++++++++++++++++++++++++++++++++ 1 file changed, 504 insertions(+) create mode 100644 text/0000-min-const-traits.md diff --git a/text/0000-min-const-traits.md b/text/0000-min-const-traits.md new file mode 100644 index 00000000000..1ca49bfccd6 --- /dev/null +++ b/text/0000-min-const-traits.md @@ -0,0 +1,504 @@ +- Feature Name: `min_const_traits` +- Start Date: 2026-09-27 +- RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) +- Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) + +## Summary +[summary]: #summary + +This proposal makes trait methods callable in const contexts: + +- Allow marking `trait` declarations as `const` implementable +- Allow marking `impl`s as const, both inherent and trait impls. + +Fully contained example: + +```rust +const trait Default { + fn default() -> Self; +} + +const impl Default for () { + fn default() {} +} + +struct Thing(T); + +const impl Default for Thing { + fn default() -> Self { Self(T::default()) } +} + +struct S {} + +const impl S { + fn default() -> T { + T::default() + } +} + +const _: () = Default::default(); + +fn main() { + let () = S::default(); + let () = Default::default(); +} +``` + +## Motivation +[motivation]: #motivation + +(copied from [oli's RFC](https://github.com/oli-obk/rfcs/blob/const-trait-impl/text/0000-const-trait-impls.md)) + +Const code is currently only able to use a small subset of Rust code, as many standard library APIs and builtin syntax require calling trait methods to work. +As an example, in const contexts, comparing values whose types do not have built-in support is not currently allowed: + +```rust +const fn foo() { + let a = [1, 2, 3]; + let b = [1, 2, 4]; + if a == b {} // ERROR: cannot call non-const operator in constant functions +} +``` + +Enabling const traits will allow these builtin language constructs to be used in const code, including but not limited to: `for` loops, `?` operator, and user-defined binary operators such as `==`. Furthermore, it will make it possible to call methods on generic types in const code, making them much more expressive. + +## Background + +(copied from [oli's RFC](https://github.com/oli-obk/rfcs/blob/const-trait-impl/text/0000-const-trait-impls.md)) + +This RFC requires familiarity with "const contexts", so you may have to read [the relevant reference section](https://doc.rust-lang.org/reference/const_eval.html#const-context) first. + +Calling functions during const eval requires those functions' bodies to only use statements that const eval can handle. While it's possible to just run any code until it hits a statement const eval cannot handle, that would mean the function body is part of its semver guarantees. Something as innocent as a logging statement would make the function uncallable during const eval. + +Thus we have a marker, `const`, to add in front of functions that requires the function body to only contain things const eval can handle. This in turn allows a `const` annotated function to be called from const contexts, as you now have a guarantee it will stay callable. + +When calling a trait method, this simple scheme (that works great for free functions and inherent methods) does not work. + +```rust +const fn default() -> T { + T::default() +} +``` + +The above shouldn't (and don't) compile. +This is because you could pass any type `T` whose `impl` could + +* mutate a global static, +* read from a file, or +* just allocate memory, + +which are all not possible right now in const code, and some can't be done in Rust in const code at all. + +It should be possible to write `default` in a way that allows it to be called in const contexts +for types whose `Default` impl's `default` method satisfies all rules that `const fn` must satisfy +(including some annotation that guarantees this won't break by accident). +It must always be possible to call `default` outside of const contexts with no limitations on the generic parameters that may be passed. + +So, we need some annotation that differentiates a `T: Default` bound from one that gives us the guarantees we're looking for. + +## Guide-level explanation +[guide-level-explanation]: #guide-level-explanation + +### Const inherent impls + +When an inherent impl is prefixed with `const`, all of its contained methods are expected to be `const`. The bodies of such functions must satisfy all of what `const fn` requires. + +```rust +struct Foo {} +const impl Foo { + fn new() -> Foo { Foo {} } +} +const NEW_FOO: Foo = Foo::new(); +// ^ OK, since `Foo::new` is in a `const impl`. +``` + + +```rust +const impl Foo { + fn incorrect() { + println!("I am silly!"); // <- Error: println isn't callable in const + } +} +``` + +### Const traits and their methods + +To allow `const impl`s and trait bounds being used in const contexts, traits must opt-in to be a `const trait`: + +```rust +const trait PartialEq { + fn eq(&self, other: &Self) -> bool; + fn ne(&self, other: &Self) -> bool { + !self.eq(other) + } +} +``` + +This will require all its default bodies to be callable in compile time, and will allow the trait bound to be used in `const impl`s: + +```rust +const impl PartialEq for u32 { + fn eq(&self, other: &u32) -> bool { + *self == *other + } +} +const impl Foo { + fn equals_self(a: &T) -> bool { + a == a + } +} + +const _: () = assert!(Foo::equals_self(&1u32); +``` + +### Generic bounds + +When inside a `const impl` or `const trait`, bounds such as `T: PartialEq` will require `T` to provide a `const impl` for `PartialEq`, if called from compile time: + +```rust +struct MyBadType {} + +impl PartialEq for MyBadType { + fn eq(&self, _other: &MyBadType) -> bool { + println!("I like to do silly things!"); + true + } +} + +const _: () = assert!(Foo::equals_self(&MyBadType {})); +// ^ errors: MyBadType doesn't have a `const impl PartialEq`, +// which is required by `equals_self` +``` + +These bounds are conditionally applied, so non-const callers will not error: + +```rust +fn main() { + assert!(Foo::equals_self(&MyBadType {})); + // ^ this is OK, because we aren't calling `equals_self` from a const context. + // So it doesn't require `const impl PartialEq` for `MyBadType`. +} +``` + +This _only_ applies to `const impl` and `const trait`, as `const fn` is not affected and retains the behavior before this RFC: + +```rust +const fn welp(_x: &T) { + // assert!(_x == _x); + // ^ this would error, as `const fn` does not allow usage of + // trait methods on generic types +} + +const _: () = welp(&MyBadType {}); +// ^ this is fine, because `welp` does not have a const impl requirement +``` + + + +### Destructors and rules for ensuring const destructors + +When a value goes out of scopes, its destructor should run. This applies to `const` contexts as well. However, some destructors are not compatible to be run in the const context, due to having non-const operations in them: + +```rust +impl Drop for MyBadType { + fn drop(&mut self) { + println!("I am silly again!"); + } +} +``` + +This gets caught by static analysis and any code that attempts to run non-const destructors will be promptly rejected. However, that also means rejecting any generic types: + +```rust +const fn drop_it(x: T) { + // ^ error: cannot drop `T` at compile time. +} +``` + +Because const traits enables more powerful generic const functions, they will inevitably require running destructors on generic types. To enable that, we use the following two rules: + +1. No `const impl`, `const trait` methods, or `const` items/blocks that call them are allowed to create any value that has a non-const destructor. +2. `const fn`s are not allowed to call `const impl` or `const trait` methods. (see [#Allowing-const-fn-to-call-const-impls](#Allowing-const-fn-to-call-const-impls) for the rationale) + + +```rust +const impl Foo { + fn new() -> Foo { + std::mem::forget(MyBadType {}); // *not* ok! .. + // ^ ..since the `Drop` impl for `MyBadType` isn't `const`. + Foo {} + } +} +const fn new_foo() -> Foo { + Foo::new(); // *not* ok! `const fn` cannot call `const impl`. +} +``` + +To remain backwards compatible with existing const items, only bodies that call `const impl` methods or `const trait` methods will be subject to this rule. (This may change at a later edition) +```rust +struct NonConstDrop; + +impl Drop for NonConstDrop { + fn drop(&mut self) { + println!("some non-const operation") + } +} + +const _: () = { + std::mem::forget(NonConstDrop); +}; +// ^ OK: doesn't call any `const impl` or `const trait` methods. + +const _: () = { + let _ = Foo::new(); + std::mem::forget(NonConstDrop); + // ^ *not* okay! +}; +``` + +Combining the two rules allows us to assume that all types (even generic ones) have a destructor that can be run in compile time, inside `const impl`s: + +```rust +const impl Foo { + fn drops_generic(_x: T) {} + // ^ OK: we're in a `const impl` +} +const fn drops_generic(_x: T) {} +// ^ *not* okay! +``` + +## Reference-level explanation +[reference-level-explanation]: #reference-level-explanation + +### Syntax details + +It is not permitted to write `const fn` within a `const impl` as it is redundant: + +```rust +const impl Foo { + const fn redundant(); + // ^ errors +} +``` + +This applies similarly for `const trait`s and trait impls: + +```rust +const trait MyTrait { + const fn redundant(); + // ^ errors +} + +const impl MyTrait for Foo { + const fn redundant() {} + // ^ errors +} +``` + +These, however, may be allowed after a future RFC if it assigns them a special meaning. + +If the trait is not const, they cannot be implemented via `const impl`s or referred to in trait bounds in `const impl`s (this includes anywhere: super traits, associated type bounds, etc): + +```rust +trait NotConstTrait {} +const impl NotConstTrait for u32 {} // *not* ok! +const impl Foo { + fn bounds() {} // *not* ok! +} +``` + +### Proving that we can call a type's trait methods in const contexts + +The trait solver parts to this are covered in the [const conditions checking chapter](https://rustc-dev-guide.rust-lang.org/effects.html) in rustc-dev-guide. + +To prove we can call a type's trait methods, we need to know whether we are calling from a `const` block or regular `const impl` fn. `const` blocks are stricter because functions need to be called in compile time even if the caller is from runtime. That could require a new bound `T: const Trait` ("always const"), but is not specified in this RFC (see [#Explicit const bounds](#Explicit-const-bounds)). + +Bounds can be "conditionally const" and "not const". "conditionally const" will require `const impl`s if called from const contexts, but won't require them if not, as explained above. This section will use `T: [const] Trait` to represent "conditionally const" bounds and `T: Trait` as "not const" bounds, but do note that it gets inferred based on whether if inside a `const impl`/`const trait` and `[const]` syntax is not exposed by this RFC. + +To prove `T: [const] Trait` for a const context, we need to have `const impl Trait for T` (with all its bounds satisfied too) or have that as a requirement for the function (`T: Trait` within `const trait`/`const impl`). + +Built-in impls should be provided, for example `const fn` should satisfy `: [const] Fn` as appropriate. + +### Keyword order + +Same as `fn`, comes before `unsafe`: + +```rust +const unsafe fn hi() {} +const unsafe trait Hi {} +const unsafe impl Hi for () {} +``` + +### Destructors + +If a type has a non-const implementation of `Drop`, it has a non-const destructor: + +```rust +struct S {} + +impl Drop for S { + fn drop(&mut self) {} +} +``` + +Any type that contains a field with a non-const destructor also has a non-const destructor: + +```rust +struct ContainsS { inner: S } +``` + +Conditions the compiler uses to prove a type can be dropped in compile time: + +1. For any type, if they have a `const impl Drop for Type` present, that is enough to prove it can be dropped. + * compiler must check at the `const impl Drop` site that all fields of such type also can be dropped at compile time. +1. Any type `T` that has a clause guaranteeing `T: Copy` can be dropped in compile time. +1. ADTs can be dropped in compile time if all its fields can be dropped in compile time (unless it has `impl Drop`). +1. Compound types can be dropped in compile time if the types they are made out of can be dropped in compile time. e.g., `(A, B)` can be dropped in compile time if and only if both `A` and `B` can be dropped in compile time. +1. Parameter types (`T` from `fn a`) and projection types (`T::A` from `fn a`) are assumed to be droppable at compile time, but only when within a `const impl` or `const trait` body. + +Proving such property is done via a built-in trait that is not exposed to users. + +Because `const fn` cannot assume parameter types and projection types as const droppable as in 5, `const impl` and `const trait` may use a separate built-in trait to distinguish such behavior for the trait solver. + +`const impl`, `const trait` methods, as well as any `const` items or blocks that call them, must have their bodies checked: no expressions may produce a value that cannot be dropped at compile time. +* This can be done by checking the types of all locals in the MIR. + +#### Opting out + +There is no opt-out per trait bound (to not require `const impl` for trait bounds). Because it is [expected](https://cel.cs.brown.edu/const-traits-analysis/) that using const traits' methods is the default and most used. This allows this proposal to be free of any additional specified syntax except `const impl` and `const trait`. + +Some traits nevertheless want to be opt out from const bounds wholesale. This includes `{Meta,Pointee,}Sized`, `Copy`, `Tuple`, auto traits, and other marker traits that do not make sense to be `const trait`s. These traits will never become `const trait`s and there can be an internal attribute `#[rustc_never_const_trait]` to allow them to be used in `const impl`s without being const traits, pending any further design on this. + +It is expected that, without allowing `const fn` to call `const impl`s, more functions will be written as associated functions. This is a reasonable workaround. Existing `fn` in the standard library, however, cannot be worked around as that would be a breaking change. So it also requires an internal attribute `#[rustc_min_const_traits_fn]` that allows the compiler to treat it like a `const impl` method: It disallows any non-const-destruct values within the body, allows calling `const impl`s, but disallows other normal `const fn` callers. + + +### When we imply `T: [const] Trait` + +The principle to apply here is to always break function bodies and not types in case we do a breaking change across editions. + +```rust +const impl Foo { + fn foo() {} +} + +const trait Bar: PartialEq { + type Assoc: PartialEq; +} +``` + +All bounds here are assumed to mean `T: [const] PartialEq`. This means all callers of `Foo::foo` must provide a type that satisfies this, and all const implementers of `Bar` must provide a type that satisfies `PartialEq`, both for the super trait and for the associated type. + +If such assumption changes to mean "non-const" bound, no callers of `Foo::foo`, or const implementers of `Bar` will need to change because they were satsifying a previously stricter requirement. + +Only function bodies that rely on these assumptions will be broken, and that appears relatively easier to migrate for edition changes. + +### Banned types and constructs + +`fn(T) -> T` function pointers, `impl Trait`, and `dyn Trait` must not appear on any `const impl`s or `const trait`s (or exposed via an unstable feature out of scope for this RFC). Self types, parameter types, return types must be walked recursively to enforce this. + + +## Drawbacks +[drawbacks]: #drawbacks + +### New feature + +We're introducing a new feature, that has new concepts to teach and new gotchas to figure out. But enabling powerful compile time computation needs this feature, such as using `Vec` in compile time, and having `for` loops and other iterator methods. + + +## Rationale, alternatives, and unresolved questions +[rationale-and-alternatives]: #rationale-and-alternatives + + +### Specifying any syntax + +It is quite true that `T: [const] Trait` is the default bound mode in these contexts. They definitely get used more often than not. Considering an opt-in scheme would have to specify a syntax that remains compatible for other opt-in schemes in the future (other effects? `T: async Trait`). This is hard. + +Opting out, similarly, requires specifying a syntax to do so. That is also hard given that it has future considerations with other effects, if we want to add them. + +### Allowing `const fn` to call `const impl`s + +Because `const fn` doesn't get to call `const impl` methods, many libraries will switch to using inherent `const impl` even if functions can be free functions. However, migrating associated `const impl` functions to and from standalone `const fn` is a breaking change for both directions. This can cause a wave of libraries essentially being stuck using associated `const impl` functions even if we enable `const fn` to use this feature in the future. + +This appears to be a reasonable trade-off: We get to specify the new semantics for `const impl` and `const trait`s and design this to add the support we need. If eventually we come up with a design that makes `const fn` usable, library authors can also deprecate the old `const impl` methods that they believe exist more naturally as `const fn`. Breaking changes for library authors is less costly than breaking changes for the Rust language. + +We could invent a new opt-in for `const fn` so that they are restricted and cannot construct any non-const-drop values. We could also specify that the restriction applies to a new edition, and only then can they call `const impl`s. These are valid and would unblock many use cases, but would require a separate syntax from `const fn` (otherwise, we could use an attribute?) + +Implicitly opting `const fn` in if they call any `const impl` has the bad effect that the function body now affects the property of the function, because it is transitively colored. If `const fn` calls a `const impl`, then any `const fn` that calls it also need to be restricted to not make any non-const-drop values. This is very difficult for both users and compiler developers. + +At time of proposal, no `const fn` use `const impl`/`const trait`s in public libraries, as such language feature is unstable/experimentally supported. We can take the most conservative path to allow developing this feature further, while exposing a set of them to the users to make many useful things. + +### Exposing `Destruct` + +This refers to the currently unstable trait `Destruct` being used to require and prove a type can be dropped in compile time. + +Naming such a trait `Destruct` is up to debate. Having more than one trait for destructors/dropping is likewise confusing. Not exposing them/specifying what it is in this proposal helps avoid bikeshed for the naming/how to reconcile duplication with the `Drop` trait. + +In the future, if Rust provides support for `Linear` types, that might help us expose types that should always be consumed instead of dropped. + +### Allowing `fn` pointers, `dyn Trait` and `impl Trait` + +It would be pretty hard to know whether we also want to implicitly require `const fn` or `const impl` for them. Properly reserving them and making them unstable appears to be a good idea until we have a more concrete story to act upon. + +## Prior art +[prior-art]: #prior-art + +Allowing const functions to call generic trait methods was originally proposed as [RFC 2632](https://github.com/rust-lang/rfcs/pull/2632) (2019-02-05), which was provisionally approved for experimentation (tracked at [#67792](https://github.com/rust-lang/rust/issues/67792)). After compiler support matured for a while, an opt-in syntax `~const` was proposed along with other changes, and resulted in a [revamped proposal](https://internals.rust-lang.org/t/pre-rfc-revamped-const-trait-impl-aka-rfc-2632/15192) (2021-08-18) posted to IRLO. + +Further experimentation and improvements to compiler support ensued. On 2024-01-07, project-const-traits was [created](https://github.com/rust-lang/team/pull/1173) as a subteam of T-compiler to organize effort around const traits implementation within the compiler. After the implementation settled, [RFC 3762](https://github.com/rust-lang/rfcs/pull/3762) opened on 2025-01-13. + +On 2025-08-20, [@fee1-dead](https://github.com/fee1-dead) published a [blogpost](https://dbeef.dev/const-trait-counterexamples/) summarized common refutations for arguments around this language feature. + +See also [Prior art](https://github.com/oli-obk/rfcs/blob/const-trait-impl/text/0000-const-trait-impls.md#prior-art) from RFC 3762. + + + +## Future possibilities +[future-possibilities]: #future-possibilities + + +### Const closures + +One should be able to define closures as callable from within const contexts, e.g. `const |x| x * 2` or similar. It is nice to have, however that requires additional syntax consideration and should be left out of this proposal. + +### Const derives + +Derives for traits should distinguish whether users want to generate `const impl`s. This ties into other language proposals to make `derive` better, and should be grouped together with those language proposals. This is because while it is easy to add `const impl` generation for built-in impls, existing user-defined `derive`s don't have a way to figure out whether a user requests a `const impl`, and we'd also need a mechanism for the user to ensure such a request is respected. + +### Const `fn` pointers, `dyn Trait`s, and `impl Trait` + +Even non-const versions of these are not currently allowed within `const impl` and `const trait` blocks, to allow maximum freedom in how we want to deal with them in a future language proposal. + +### Explicit const bounds + +There could also be a bound `T: const Trait` that allows calling `T`'s methods within a const block (unlike `const fn`, a `const` block would always be evaluated in compile time, so it would be a stricter bound). However that is orthogonal to the feature we're proposing, and I intend this proposal to be as minimal as possible. + + + +## Credits + +Huge thanks to [oli](https://github.com/oli-obk) for pushing this proposal and sticking around for 7 years, laying out the basis for much of the language design and compiler implementation. + +Thanks [Michael Goulet](https://github.com/compiler-errors), [fee1-dead](https://github.com/fee1-dead), [oli](https://github.com/oli-obk), [fmease](https://github.com/fmease), and many others that helped refine the experimental compiler support for const traits, that in turn informed many language design choices in syntax and semantics. + +Thanks to [nikomatsakis](https://github.com/nikomatsakis/), [TC](https://github.com/traviscross), and many others who have participated in language discussions and helped shaped this proposal. + +Thanks to [Will Crichton](https://github.com/willcrichton/) and [Jack Huey](https://github.com/jackh726) for running [the survey](https://cel.cs.brown.edu/const-traits-analysis/) to help identify what is a reasonable default for const bound's semantics, that informed the direction of this RFC. \ No newline at end of file From fd1c1a6c8cc6cd91b1ddc1c805ef8441eca9a5e3 Mon Sep 17 00:00:00 2001 From: Deadbeef Date: Sun, 27 Sep 2026 17:10:01 -0400 Subject: [PATCH 2/7] remove comment --- text/0000-min-const-traits.md | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/text/0000-min-const-traits.md b/text/0000-min-const-traits.md index 1ca49bfccd6..fe2e1da3fe7 100644 --- a/text/0000-min-const-traits.md +++ b/text/0000-min-const-traits.md @@ -472,27 +472,6 @@ Even non-const versions of these are not currently allowed within `const impl` a There could also be a bound `T: const Trait` that allows calling `T`'s methods within a const block (unlike `const fn`, a `const` block would always be evaluated in compile time, so it would be a stricter bound). However that is orthogonal to the feature we're proposing, and I intend this proposal to be as minimal as possible. - - ## Credits Huge thanks to [oli](https://github.com/oli-obk) for pushing this proposal and sticking around for 7 years, laying out the basis for much of the language design and compiler implementation. From b86545a9ef1ecf3d657607891329180b4a5627fd Mon Sep 17 00:00:00 2001 From: Deadbeef Date: Tue, 29 Sep 2026 12:34:31 -0400 Subject: [PATCH 3/7] rework the destructors section to use the implicit Destruct bounds approach --- text/0000-min-const-traits.md | 134 ++++++++++++++++++---------------- 1 file changed, 70 insertions(+), 64 deletions(-) diff --git a/text/0000-min-const-traits.md b/text/0000-min-const-traits.md index fe2e1da3fe7..7da3f5349a4 100644 --- a/text/0000-min-const-traits.md +++ b/text/0000-min-const-traits.md @@ -151,9 +151,25 @@ const impl Foo { const _: () = assert!(Foo::equals_self(&1u32); ``` +Because existing semantics don't allow free `const fn`s to call trait methods on trait bounds, an `#[const_bounds]` attribute is +necessary for opting in to using them: + +```rust +const fn equals_self_bad(a: &T) -> bool { + a == a + //^ ERROR: cannot call non-const operator in constant functions +} + +#[const_bounds] +const fn equals_self(a: &T) -> bool { + a == a + //^ ok! +} +``` + ### Generic bounds -When inside a `const impl` or `const trait`, bounds such as `T: PartialEq` will require `T` to provide a `const impl` for `PartialEq`, if called from compile time: +When calling a method on a `const impl` or `const trait` or `#[const_bounds] const fn`, bounds such as `T: PartialEq` will require `T` to provide a `const impl` for `PartialEq`, if called from compile time: ```rust struct MyBadType {} @@ -180,13 +196,13 @@ fn main() { } ``` -This _only_ applies to `const impl` and `const trait`, as `const fn` is not affected and retains the behavior before this RFC: +`const fn` without the `#[const_bounds]` attribute is not affected and retains the behavior before this RFC: ```rust const fn welp(_x: &T) { // assert!(_x == _x); - // ^ this would error, as `const fn` does not allow usage of - // trait methods on generic types + // ^ this would error, as `const fn` (without `#[const_bounds]`) does not + // allow usage of trait methods on generic types } const _: () = welp(&MyBadType {}); @@ -194,7 +210,6 @@ const _: () = welp(&MyBadType {}); ``` - ### Destructors and rules for ensuring const destructors When a value goes out of scopes, its destructor should run. This applies to `const` contexts as well. However, some destructors are not compatible to be run in the const context, due to having non-const operations in them: @@ -207,7 +222,7 @@ impl Drop for MyBadType { } ``` -This gets caught by static analysis and any code that attempts to run non-const destructors will be promptly rejected. However, that also means rejecting any generic types: +Attempting to drop these types gets caught by static analysis and will be promptly rejected. However, that also means rejecting any generic types: ```rust const fn drop_it(x: T) { @@ -215,56 +230,49 @@ const fn drop_it(x: T) { } ``` -Because const traits enables more powerful generic const functions, they will inevitably require running destructors on generic types. To enable that, we use the following two rules: - -1. No `const impl`, `const trait` methods, or `const` items/blocks that call them are allowed to create any value that has a non-const destructor. -2. `const fn`s are not allowed to call `const impl` or `const trait` methods. (see [#Allowing-const-fn-to-call-const-impls](#Allowing-const-fn-to-call-const-impls) for the rationale) - +Because const traits enables more powerful generic const functions, they will inevitably require running destructors on generic types. To enable that, all `const impl`, `const trait`, and `#[const_bounds] const fn` will require all parameters types provided to have a destructor callable in compile time: ```rust -const impl Foo { - fn new() -> Foo { - std::mem::forget(MyBadType {}); // *not* ok! .. - // ^ ..since the `Drop` impl for `MyBadType` isn't `const`. - Foo {} - } -} -const fn new_foo() -> Foo { - Foo::new(); // *not* ok! `const fn` cannot call `const impl`. +#[const_bounds] +const fn drop_it(x: T) { + // ^ fine, `#[const_bounds]` will insert a requirement for `T` droppable in compile time } ``` -To remain backwards compatible with existing const items, only bodies that call `const impl` methods or `const trait` methods will be subject to this rule. (This may change at a later edition) +This includes associated types for all traits bounded in scope, as well as `Self`: + ```rust -struct NonConstDrop; +#[const_bounds] +const fn drop_iterator(x: T) { + let iter = x.into_iter(); + drop(iter.next()); + // ^ fine, `T::Item` is required to be const-droppable. + drop(iter); + // ^ fine, `T::IntoIter` is required to be const-droppable. +} -impl Drop for NonConstDrop { - fn drop(&mut self) { - println!("some non-const operation") +const trait MyTrait { + fn drops_self(self) { + // ^ fine, `Self` is required to be const-droppable. } } - -const _: () = { - std::mem::forget(NonConstDrop); -}; -// ^ OK: doesn't call any `const impl` or `const trait` methods. - -const _: () = { - let _ = Foo::new(); - std::mem::forget(NonConstDrop); - // ^ *not* okay! -}; ``` -Combining the two rules allows us to assume that all types (even generic ones) have a destructor that can be run in compile time, inside `const impl`s: +This requirement can be satisfied when providing a parameter type in a `#[const_bounds] const fn`, `const trait`, or `const impl` as the requirement is carried over, or when providing a concrete type that is known to be droppable in compile time. ```rust -const impl Foo { - fn drops_generic(_x: T) {} - // ^ OK: we're in a `const impl` +#[const_bounds] +const fn drop_iterator_forwarded(x: T) { + drop_iterator(x) // <- fine, we share the same requirements as `drop_iterator` +} + +const fn drop_wrong(x: T) { + drop_it(x) // <- ERROR, we don't have the requirements as we are not `#[const_bounds]` +} + +const fn drop_concrete() { + drop_it(()) // <- fine, we know `()` to be droppable in compile time } -const fn drops_generic(_x: T) {} -// ^ *not* okay! ``` ## Reference-level explanation @@ -354,23 +362,34 @@ Conditions the compiler uses to prove a type can be dropped in compile time: 1. Any type `T` that has a clause guaranteeing `T: Copy` can be dropped in compile time. 1. ADTs can be dropped in compile time if all its fields can be dropped in compile time (unless it has `impl Drop`). 1. Compound types can be dropped in compile time if the types they are made out of can be dropped in compile time. e.g., `(A, B)` can be dropped in compile time if and only if both `A` and `B` can be dropped in compile time. -1. Parameter types (`T` from `fn a`) and projection types (`T::A` from `fn a`) are assumed to be droppable at compile time, but only when within a `const impl` or `const trait` body. -Proving such property is done via a built-in trait that is not exposed to users. +Proving such property is done via a built-in trait that is not exposed to users. We name it here as `[const] Destruct` although that is not a name this RFC intends to specify (only used for illustration). -Because `const fn` cannot assume parameter types and projection types as const droppable as in 5, `const impl` and `const trait` may use a separate built-in trait to distinguish such behavior for the trait solver. +`const trait`, `const impl`, and `#[const_bounds] const fn` get implicit `[const] Destruct` bounds on all parameter types, all associated types, and all self types if they are bounded: -`const impl`, `const trait` methods, as well as any `const` items or blocks that call them, must have their bodies checked: no expressions may produce a value that cannot be dropped at compile time. -* This can be done by checking the types of all locals in the MIR. +```rust +struct Wrapper(T); +#[const_bounds] +const fn foo() // -> A: [const] Destruct, B: [const] Destruct +where + A: IntoIterator, // -> A::Item: [const] Destruct, A::IntoIter: [const] Destruct + // --> ::Item: [const] Destruct + // ^ (the implicit bound is recursively generated for associated types) + Wrapper: Clone, // -> Wrapper: [const] Destruct + Wrapper<[C; 2]>: IntoIterator, // -> Wrapper<[C; 2]>: [const] Destruct + // -> Wrapper::<[C; 2]>::Item: [const] Destruct, Wrapper::<[C; 2]>::IntoIter: [const] Destruct + // --> ::IntoIter as Iterator>::Item: [const] Destruct +{ -#### Opting out +} +``` -There is no opt-out per trait bound (to not require `const impl` for trait bounds). Because it is [expected](https://cel.cs.brown.edu/const-traits-analysis/) that using const traits' methods is the default and most used. This allows this proposal to be free of any additional specified syntax except `const impl` and `const trait`. -Some traits nevertheless want to be opt out from const bounds wholesale. This includes `{Meta,Pointee,}Sized`, `Copy`, `Tuple`, auto traits, and other marker traits that do not make sense to be `const trait`s. These traits will never become `const trait`s and there can be an internal attribute `#[rustc_never_const_trait]` to allow them to be used in `const impl`s without being const traits, pending any further design on this. +#### Opting out -It is expected that, without allowing `const fn` to call `const impl`s, more functions will be written as associated functions. This is a reasonable workaround. Existing `fn` in the standard library, however, cannot be worked around as that would be a breaking change. So it also requires an internal attribute `#[rustc_min_const_traits_fn]` that allows the compiler to treat it like a `const impl` method: It disallows any non-const-destruct values within the body, allows calling `const impl`s, but disallows other normal `const fn` callers. +There is no opt-out per trait bound (to not require `const impl` for trait bounds). Because it is [expected](https://cel.cs.brown.edu/const-traits-analysis/) that using const traits' methods is the default and most used. This allows this proposal to be free of any additional specified syntax except `const impl` and `const trait`, as well as `#[const_bounds]` for an opt-in on `const fn`. +Some traits nevertheless want to be opt out from const bounds wholesale. This includes `{Meta,Pointee,}Sized`, `Copy`, `Tuple`, auto traits, and other marker traits that do not make sense to be `const trait`s. These traits will never become `const trait`s and there can be an internal attribute `#[rustc_never_const_trait]` to allow them to be used in `const impl`s without being const traits, pending any further design on this. ### When we imply `T: [const] Trait` @@ -396,7 +415,6 @@ Only function bodies that rely on these assumptions will be broken, and that app `fn(T) -> T` function pointers, `impl Trait`, and `dyn Trait` must not appear on any `const impl`s or `const trait`s (or exposed via an unstable feature out of scope for this RFC). Self types, parameter types, return types must be walked recursively to enforce this. - ## Drawbacks [drawbacks]: #drawbacks @@ -415,18 +433,6 @@ It is quite true that `T: [const] Trait` is the default bound mode in these cont Opting out, similarly, requires specifying a syntax to do so. That is also hard given that it has future considerations with other effects, if we want to add them. -### Allowing `const fn` to call `const impl`s - -Because `const fn` doesn't get to call `const impl` methods, many libraries will switch to using inherent `const impl` even if functions can be free functions. However, migrating associated `const impl` functions to and from standalone `const fn` is a breaking change for both directions. This can cause a wave of libraries essentially being stuck using associated `const impl` functions even if we enable `const fn` to use this feature in the future. - -This appears to be a reasonable trade-off: We get to specify the new semantics for `const impl` and `const trait`s and design this to add the support we need. If eventually we come up with a design that makes `const fn` usable, library authors can also deprecate the old `const impl` methods that they believe exist more naturally as `const fn`. Breaking changes for library authors is less costly than breaking changes for the Rust language. - -We could invent a new opt-in for `const fn` so that they are restricted and cannot construct any non-const-drop values. We could also specify that the restriction applies to a new edition, and only then can they call `const impl`s. These are valid and would unblock many use cases, but would require a separate syntax from `const fn` (otherwise, we could use an attribute?) - -Implicitly opting `const fn` in if they call any `const impl` has the bad effect that the function body now affects the property of the function, because it is transitively colored. If `const fn` calls a `const impl`, then any `const fn` that calls it also need to be restricted to not make any non-const-drop values. This is very difficult for both users and compiler developers. - -At time of proposal, no `const fn` use `const impl`/`const trait`s in public libraries, as such language feature is unstable/experimentally supported. We can take the most conservative path to allow developing this feature further, while exposing a set of them to the users to make many useful things. - ### Exposing `Destruct` This refers to the currently unstable trait `Destruct` being used to require and prove a type can be dropped in compile time. From ce086a5645fae7fbbd3c57cdb74887aeef7a6db9 Mon Sep 17 00:00:00 2001 From: Deadbeef Date: Tue, 29 Sep 2026 12:53:48 -0400 Subject: [PATCH 4/7] reword --- text/0000-min-const-traits.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-min-const-traits.md b/text/0000-min-const-traits.md index 7da3f5349a4..0bb23ffd71a 100644 --- a/text/0000-min-const-traits.md +++ b/text/0000-min-const-traits.md @@ -134,7 +134,7 @@ const trait PartialEq { } ``` -This will require all its default bodies to be callable in compile time, and will allow the trait bound to be used in `const impl`s: +This will require all its default bodies to only perform const operations (allowing `const impl` to use these default bodies), and will allow the trait bound to be used in `const impl`s: ```rust const impl PartialEq for u32 { From 47ad802d1e7326f341df2642fb8cf4b6c8ed75db Mon Sep 17 00:00:00 2001 From: Deadbeef Date: Tue, 29 Sep 2026 12:58:17 -0400 Subject: [PATCH 5/7] elaborate on banned types and constructs --- text/0000-min-const-traits.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/text/0000-min-const-traits.md b/text/0000-min-const-traits.md index 0bb23ffd71a..c54a19791ef 100644 --- a/text/0000-min-const-traits.md +++ b/text/0000-min-const-traits.md @@ -413,7 +413,9 @@ Only function bodies that rely on these assumptions will be broken, and that app ### Banned types and constructs -`fn(T) -> T` function pointers, `impl Trait`, and `dyn Trait` must not appear on any `const impl`s or `const trait`s (or exposed via an unstable feature out of scope for this RFC). Self types, parameter types, return types must be walked recursively to enforce this. +`fn(T) -> T` function pointers, `impl Trait`, and `dyn Trait` must not appear on any `const impl`s, `const trait`s, or `#[const_bounds] const fn`s (or exposed via an unstable feature out of scope for this RFC). Self types, parameter types, return types must be walked recursively to enforce this. + +Furthermore, their bodies may not perform `dyn Trait` unsizing coercions, create closures, or convert function items to function pointers. ## Drawbacks [drawbacks]: #drawbacks From 630757df56fbf992f038bb96633c542559ffe399 Mon Sep 17 00:00:00 2001 From: Deadbeef Date: Tue, 29 Sep 2026 13:13:40 -0400 Subject: [PATCH 6/7] clarify allowed usage sites of #[const_bounds], elaborate on implied bounds explanation --- text/0000-min-const-traits.md | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/text/0000-min-const-traits.md b/text/0000-min-const-traits.md index c54a19791ef..f45e8536949 100644 --- a/text/0000-min-const-traits.md +++ b/text/0000-min-const-traits.md @@ -315,6 +315,22 @@ const impl Foo { } ``` +`#[const_bounds]` is only allowed on free `const fn`, usage of it on any other items are not allowed: + +```rust +#[const_bounds] // ok! +const fn free_fn() {} + +#[const_bounds] // not ok! +struct Foo; + +#[const_bounds] // not ok! +impl Foo { + #[const_bounds] // not ok! use `const impl` + const fn assoc_fn() {} +} +``` + ### Proving that we can call a type's trait methods in const contexts The trait solver parts to this are covered in the [const conditions checking chapter](https://rustc-dev-guide.rust-lang.org/effects.html) in rustc-dev-guide. @@ -405,7 +421,7 @@ const trait Bar: PartialEq { } ``` -All bounds here are assumed to mean `T: [const] PartialEq`. This means all callers of `Foo::foo` must provide a type that satisfies this, and all const implementers of `Bar` must provide a type that satisfies `PartialEq`, both for the super trait and for the associated type. +All bounds here are assumed to mean `[const] PartialEq` (this includes `T: [const] PartialEq`, `Self: [const] PartialEq`, and `Self::Assoc: [const] PartialEq`). This means all callers of `Foo::foo` must provide a type that satisfies this, and all const implementers of `Bar` must provide a type that satisfies `PartialEq`, both for the super trait and for the associated type. If such assumption changes to mean "non-const" bound, no callers of `Foo::foo`, or const implementers of `Bar` will need to change because they were satsifying a previously stricter requirement. From 1d142f22d1c14996028bb157b580bb5172b8ef03 Mon Sep 17 00:00:00 2001 From: Deadbeef Date: Tue, 29 Sep 2026 13:30:21 -0400 Subject: [PATCH 7/7] add comparison to previous proposals --- text/0000-min-const-traits.md | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/text/0000-min-const-traits.md b/text/0000-min-const-traits.md index f45e8536949..f74f085a75c 100644 --- a/text/0000-min-const-traits.md +++ b/text/0000-min-const-traits.md @@ -463,6 +463,16 @@ In the future, if Rust provides support for `Linear` types, that might help us e It would be pretty hard to know whether we also want to implicitly require `const fn` or `const impl` for them. Properly reserving them and making them unstable appears to be a good idea until we have a more concrete story to act upon. +### Comparison to previous proposals + +Past proposals required users to opt-in to `T: [const] Trait` bounds to call its methods in compile time, making it consistent with other language constructs such as `fn()` pointers and `dyn Trait`s which will likely also require opt-in in the future. However, most `const fn` will want to use `[const] Trait` with very rare exceptions. It makes sense to make the most commonly used semantic be the default. + +This is the same reasoning applyied to implicit `[const] Destruct` desugaring: Throughout the experiment with using const traits in the standard library, `[const] Destruct` was needed in many situations, so implying them implicitly is beneficial for reducing syntax noise. + +Furthermore, as this RFC proposes to ban the use of `fn()` pointers and other features that might need changes to allow them to be used in compile time, we can design these in the future that makes the story consistent. + +On the other hand, it might be preferable to make the behavior of requiring `const impl` explicit, and make the less restrictive bound the default. However, making the more restrictive bound the default can also be beneficial: the library author can always choose to relax the bound later without causing a breaking change. + ## Prior art [prior-art]: #prior-art @@ -474,12 +484,9 @@ On 2025-08-20, [@fee1-dead](https://github.com/fee1-dead) published a [blogpost] See also [Prior art](https://github.com/oli-obk/rfcs/blob/const-trait-impl/text/0000-const-trait-impls.md#prior-art) from RFC 3762. - - ## Future possibilities [future-possibilities]: #future-possibilities - ### Const closures One should be able to define closures as callable from within const contexts, e.g. `const |x| x * 2` or similar. It is nice to have, however that requires additional syntax consideration and should be left out of this proposal. @@ -504,4 +511,4 @@ Thanks [Michael Goulet](https://github.com/compiler-errors), [fee1-dead](https:/ Thanks to [nikomatsakis](https://github.com/nikomatsakis/), [TC](https://github.com/traviscross), and many others who have participated in language discussions and helped shaped this proposal. -Thanks to [Will Crichton](https://github.com/willcrichton/) and [Jack Huey](https://github.com/jackh726) for running [the survey](https://cel.cs.brown.edu/const-traits-analysis/) to help identify what is a reasonable default for const bound's semantics, that informed the direction of this RFC. \ No newline at end of file +Thanks to [Will Crichton](https://github.com/willcrichton/) and [Jack Huey](https://github.com/jackh726) for running [the survey](https://cel.cs.brown.edu/const-traits-analysis/) to help identify what is a reasonable default for const bound's semantics, that informed the direction of this RFC.