Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Open
Changes from 1 commit
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
545099b
Add const trait impl RFC
oli-obk Dec 13, 2024
290d7fe
Add per-method annotations alternative
oli-obk Jan 13, 2025
0c59930
s/maybe/conditionally/
oli-obk Jan 14, 2025
d50c8ef
Clarify some things that came up in reviews
oli-obk Jan 14, 2025
ff7fabe
const closures
oli-obk Jan 14, 2025
64c0773
Clarify `Destruct` rules
oli-obk Jan 14, 2025
f7cc5e6
Elaborate on some extensions
oli-obk Jan 14, 2025
fe97e3f
Update text/0000-const-trait-impls.md
oli-obk Jan 18, 2025
83f31f7
Address reviews
oli-obk Jan 20, 2025
8d9f649
Elaborate where `~const Trait` bounds can be used
oli-obk Jan 20, 2025
328655a
Add comparison for `?const` example
oli-obk Jan 20, 2025
fb621b6
`dyn ~const Trait` as an argument for implicit `~const Destruct`
oli-obk Feb 6, 2025
73e06e4
Const fn traits and not doing this RFC
oli-obk Feb 6, 2025
dc1cb64
(const) bounds
oli-obk Mar 7, 2025
7da95b8
require `(const) fn` on all methods
oli-obk Mar 7, 2025
73db002
Address review comments
oli-obk Mar 7, 2025
58343dd
typo: `prexisting -> preexisting`
obi1kenobi Mar 21, 2025
0a82e9e
Update text/0000-const-trait-impls.md
oli-obk Mar 31, 2025
c1981e4
Add derives
oli-obk Mar 31, 2025
cb7f02d
Move back to an explicit annotation on traits and impls
oli-obk Mar 31, 2025
bc5cc29
Typos
oli-obk Mar 31, 2025
e18f475
RPIT
oli-obk Mar 31, 2025
fdd11fb
Do not allow `const` refinement of `(const)` methods in impls
oli-obk Apr 16, 2025
53319c4
change to `[const]` and remove per-method non-constness per latest di…
fee1-dead Jun 25, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add per-method annotations alternative
  • Loading branch information
oli-obk committed Jan 14, 2025
commit 290d7fed067069302a7d2ee5e4fd9659749abe36
42 changes: 41 additions & 1 deletion text/0000-const-trait-impls.md
Copy link
Member

@fmease fmease Mar 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to see #[derive]'ing being discussed in this RFC, it's notably absent from it and shouldn't be an afterthought in my opinion.

Namely, there should be a canonical/conventional mechanism for deriving a "const trait impl" from/for an ADT.
Whether such an impl would be (unconditionally) const, conditionally const, or either depending on a "flag" would be a point for discussion.

It should be quite obvious why you'd want to const-derive a trait and why #[derive] itself shouldn't derive "conditionally const trait impls" by default.


As for the concrete design, there should be a canonical/conventional way to signal to the (built-in or user-defined) derive macro that a "const" trait impl is requested.

As @petrochenkov noted in rust-lang/rust#118580 (comment), rust-lang/rust#118580 (comment) and rust-lang/rust#118580 (comment) there are three extensible candidates:

  1. Leveraging helper attributes. At call sites, that might look like

    #[derive(One, Two)]
    #[one(const), two(const)]

    I guess. At def sites, it would just look like

    #[proc_macro_derive(One, attributes(one))]
    pub fn derive_one(_item: TokenStream) -> TokenStream {}

    That design wouldn't require any additional changes to the language.

  2. Giving derive macros a second input stream. At call sites, that might look like

    #[derive(One(const), Two(const))]

    At def sites:

    #[proc_macro_derive(One)]
    pub fn derive_one(_item: TokenStream, _arg: TokenStream) -> TokenStream {}

    Note that the one-argument form would still exist for backward compatibility. This arity-based dispatch is very much possible for proc macros. See e.g., PR Provide a way for custom derives to know if they were invoked via #[derive_const] rust#118580. Of course, we could choose to do something different here but in any case I'd prefer if we didn't force a new edition for this.

  3. Passing another input stream via "global data" (which is what e.g., Span::call_site() uses under the hood). At call sites, that might look like

    #[derive(One(const), Two(const))]

    At def sites:

    #[proc_macro_derive(One)]
    pub fn derive_one(_item: TokenStream) -> TokenStream {
       let _arg/*: TokenStream */ = extra_input_stream();}

For context, the compiler currently features the placeholder syntax #[derive_const] under the experimental feature derive_const. Obviously, that syntax is quite awkward and non-extensible (to other potential effects or data in general we might want to pass to proc macros in the future).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the derive syntax should reflect the bound syntax: #[derive(const Trait)].

Copy link
Contributor

@traviscross traviscross Mar 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be quite obvious... why #[derive] itself shouldn't derive "conditionally const trait impls" by default.

Perhaps you could elaborate on this. I can think of some reasons, but I want to be sure I've thought of all the reasons.

I think the derive syntax should reflect the bound syntax: #[derive(const Trait)].

For a related syntax discussion, see:

Copy link
Contributor

@Jules-Bertholet Jules-Bertholet Mar 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For a related syntax discussion, see #3715

unsafe is a property of the derivation operation there, while const is a property of the resulting derived impl here. The syntaxes for each should be different, to reflect that distinction.

Copy link
Member

@fmease fmease Mar 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be quite obvious... why #[derive] itself shouldn't derive "conditionally const trait impls" by default.

Perhaps you could elaborate on this. I can think of some reasons, but I want to sure I've thought of all the reasons.

Yes, of course. For the sake of argument, let's assume that this RFC establishes the convention for derive macros to generate const trait impls either always or just by default.

  1. It would create a medium-term divide in the ecosystem: The large corpus of derive macros written by users before the hypothetical stabilization of feature const_trait_impl would naturally continue to generate non-const trait impls until migrated (which takes time) contrary to the ones created after it. This could be perceived as confusing by newcomers and frustrating by seasoned Rust devs.
  2. If not made an edition item, it would implicitly and practically irrevocably alter the public API of all library crates which expose types to which built-in derive macros (from core / std) were applied as suddenly said crates would promise more (conditionally) const impls which they can't go back on without a major version update.
  3. (Conditionally) const trait impls obviously come with more requirements therefore "less can be derived (by default)". Think less about type parameters and more about the types of priv/pub fields which — from a probability perspective — likely only impl the trait in question "non-const-ly".
  4. Assuming that there's no opt-out (unlikely, I admit), going forward there would no longer be a canonical / conventional way to derive non-const trait impls (which are convenient and obviously allow for greater leeway wrt. the evolvement of the implementation) forcing users to write those impls by hand (possibly error prone) or to create their own conventions and mechanisms.
  5. Assuming that there's an opt-out, then deriving would still no longer mirror manual impls wrt. constness since constness is notoriously opt-in. That would also mean finding new syntax that's unlike anything preexisting (think #[derive(nonconst Trait)] or similar).

Feel free to correct me on / contest any of these points, some of these might be a bit weak or even irrelevant. I threw this together quickly, so yeah.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're sticking with the already implemented #[derive_const(Trait)] syntax and leaving it to future RFCs after unsafe derives are finalized to come up with something better

Copy link
Member

@fmease fmease Mar 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

derive_const only works for built-in derive macros though, are you saying that we won't support user-defined const-derives in the foreseeable future (rust-lang/rust#118304 (blocked by design concerns or rather total lack of design))?

Copy link
Member

@fee1-dead fee1-dead May 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: A lot of the discussion around the language design is happening on Zulip, under the t-lang/effects stream. The latest major proposal discussion can be found at https://rust-lang.zulipchat.com/#narrow/channel/328082-t-lang.2Feffects/topic/All.20that's.20old.20is.20new.20again. It would be nice if before commenting on this RFC one could check if this topic has been discussed on the Zulip stream before :) When there appears to be a consensus forming from community members + the lang team, the proposal should then be updated.

Original file line number Diff line number Diff line change
Expand Up @@ -641,6 +641,45 @@ With the opt-out scheme that would still compile, but suddenly require callers t
The safe default (and the one folks are used to for a few years now), is that trait bounds just work, you just
can't call methods on them. To get more capabilities, you add more syntax. Thus the opt-out approach was not taken.

## Per-method constness instead of per-trait

We could require trait authors to declare which methods can be const:

```rust
trait Default {
const fn default() -> Self;
}
```

This has two major advantages:

* you can now have const and non-const methods in your trait without requiring an opt-out
* you can add new methods with default bodies and don't have to worry about new kinds of breaking changes

The specific syntax given here may be confusing though, as it looks like the function is always const, but
implementations can use non-const impls and thus make the impl not usable for `T: ~const Trait` bounds.

Though this means that changing a non-const fn in the trait to a const fn is a breaking change, as the user may
have that previous-non-const fn as a non-const fn in the impl, causing the entire impl now to not be usable for
`T: ~const Trait` anymore.

See also: out of scope RTN notation in [Unresolved questions](#unresolved-questions)

## Per-method and per-trait constness together:

To get the advantages of the per-method constness alternative above, while avoiding the new kind of breaking change, we can require per-method and per-trait constness:

A mixed version of the above could be

```rust
const trait Foo {
const fn foo();
fn bar();
}
```

where you still need to annotate the trait, but also annotate the const methods.

# Prior art
[prior-art]: #prior-art

Expand Down Expand Up @@ -669,9 +708,10 @@ can't call methods on them. To get more capabilities, you add more syntax. Thus
* `T: Iterator<Item = U>` and don't require `where T: Iterator, <T as Iterator>::Item = U`.
* `T: Iterator<Item: Debug>` and don't require `where T: Iterator, <T as Iterator>::Item: Debug`.
* RTN for per-method bounds: `T: Trait<some_fn(..): ~const Fn(A, B) -> C>` could supplement this feature in the future.
* Alternatively `where <T as Trait>::some_fn(..): ~const` or `where <T as Trait>::some_fn \ {const}`.
* Very verbose (need to specify arguments and return type).
* Want short hand sugar anyway to make it trivial to change a normal function to a const function by just adding some minor annotations.
* Significantly would delay const trait stabilization.
* Significantly would delay const trait stabilization (by years).
* Usually requires editing the trait anyway, so there's no "can constify impls without trait author opt in" silver bullet.
* New RTN-like per-method bounds: `T: Trait<some_fn(_): ~const>`.
* Unclear if soundly possible.
Expand Down