Skip to content

RFC: const functions in traits #3490

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Changes from all commits
Commits
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
232 changes: 232 additions & 0 deletions text/0000-const-fn-in-trait.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
- Feature Name: `const_fn_in_trait`
- Start Date: 2023-09-15
- RFC PR: [rust-lang/rfcs#3490](https://github.com/rust-lang/rfcs/pull/3490)
- Rust Issue:
[rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000)

# Summary

[summary]: #summary

This feature allows marking functions in traits as `const`. Users of the trait
will be able to use these functions in const contexts.

# Motivation

[motivation]: #motivation

Currently, there is no implementation for high-level generic programming in
const contexts. Typically traits provide generic interfaces in Rust, but they do
not support `const` methods.

This RFC proposes allowing functions in a trait to be marked `const`, meaning
they can be called from a const context like other const functions. Use cases
include:

- Reducing code duplication in const contexts
- Providing a constructor for generic static object, e.g. C-style plugin
registration
- Subtraits that want to provide defaults based on supertraits
Copy link
Member

@fee1-dead fee1-dead Oct 6, 2023

Choose a reason for hiding this comment

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

I'm not sure what this use case is and why it relates to const functions in traits, could you specify?

- Compile-time checking of object properties
- Logically mononolithic traits that need to be split in order to support const
actions

Workarounds typically involve a combination of wrapper functions, macros, and
associated consts. This RFC will eliminate the need for such workarounds.
Comment on lines +34 to +35
Copy link
Contributor

Choose a reason for hiding this comment

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

I feel it may be useful to note some of these workarounds?

Essentially, I'm searching for an illustration of what is possible (although maybe hugely inconvenient and ugly) in current Rust, and what new possibilities would be enabled by this proposal, if any?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Do you mean that you are looking for a sample of what a solution might look like today? Or just a more in depth explanation from the list?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Maybe just a better explanation of each workaround or the use cases would be best, current examples are usually pretty large and vary a lot

Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm, mostly I fear that there may be a lot of edge-cases that have not yet been considered in the proposal?

And then I think that if I saw a few instances of "this thing can already be done today, just not as cleanly", then it'd assuage my worries on that front.


# Guide-level explanation

[guide-level-explanation]: #guide-level-explanation

Functions within a trait can be marked const:

```rust
trait GlobalState {
/// Create a state that will be held in a global static
const fn build(base_value: u32) -> State;
}
```

This indicates that all implementers must provide const functions:

```rust
struct Bar;

impl GlobalState for Bar {
const fn build(base_value: u32) -> State { /* ... */ }
}
```

And then the function can be called in const contexts, including as generic
calls within other const functions:

```rust
/// Add a named item to our global state register
const fn register_state<T: GlobalState>(name: &'static str) {
// ...
STATES[n] = (name, T::build(n as u32))
}

/// Or, use with a single item
static DEFAULT_STATE: State = MyFavoriteStruct::build(42);
```

The rules for what is allowed are the same as for other `const` functions. At
runtime there is no difference with non-`const` trait functions.

`const` and non-const functions can coexist within the same trait, i.e. one
function being const does not mean all functions must be const.

# Reference-level explanation

[reference-level-explanation]: #reference-level-explanation

Trait functions will need to track an additional attribute that indicates
`const`ness. All implementers must match the same `const`ness of the original
trait's function definitions.

After monomorphization, these functions will be evaluated the same way as
standard `const` functions when needed at CTFE. This additional metadata can be
stripped in all other cases and the function will act the same as a non-const
function.
Comment on lines +89 to +91
Copy link
Member

Choose a reason for hiding this comment

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

I don't think that is what we do with const functions? IIRC marking a function has const can change inliner behavior, but I might just be misremembering.


In short, this:

```rust
trait Foo {
const fn foo(&self) -> u32;
}

impl Foo for Bar { /* ... */ }
```

Should be effectively treated the same as this at compile time:

```rust
const fn bar_as_foo_foo(bar: &Bar) -> u32 { /* ... */ }
```

And as this at runtime:

```rust
trait Foo {
/* non-const */ fn foo(&self) -> u32;
}
```
## Relationship with the Keyword Generics Initiative

The [keyword generics initiative] or effects initiative proposes a way to have
optional `const`ness and `async`ness on trait functions, roughly:


```rust
// Note that syntax has changed a few times, including `~const`
const<C> trait SometimesConstFoo {
const <C> fn sometimes_const_foo(&self) -> u32
}
Comment on lines +124 to +126
Copy link
Member

Choose a reason for hiding this comment

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

This is not equivalent to const functions in traits, because there is no parameterization based on constness on the trait. Under effects, const functions in traits will become generic over constness itself, but not the traits (but we still treat it as a non-generic in different parts of rustc, something like foo<'a>)

```

This RFC aims to extract an extremely minimal subset of effects in order to make
it available sooner, similar to [`async-fn-in-trait`]. Part of why this proposed
design is so minimal is to avoid possible conflicts with effects.

# Drawbacks

[drawbacks]: #drawbacks

This feature requires tracking more information related to trait definition and
usage, but this should be a relatively maintenance burden.
Comment on lines +137 to +138
Copy link
Member

Choose a reason for hiding this comment

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

relatively low?


There is also potential user confusion due to possible more content in a `trait`
block, as well as the question "does this need to be const". However, teaching
about `const`ness that applies to standard functions will generally apply here.

# Rationale and alternatives

[rationale-and-alternatives]: #rationale-and-alternatives

There is currently no way to create generic functions that can be used in const
contexts. Workarounds exist but they are typically awkward, using a combination
of wrapper functions and macros to produce similar results.

Adding this feature approaches the goal of "Rust working like one would expect
it to work". That is, functions in traits generally act similar to functions
outside of traits, and this feature serves to bridge one of the final gaps
Copy link
Member

Choose a reason for hiding this comment

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

looks like you didn't finish this sentence here


This feature is small so there are no real alternatives outside of the status
quo workarounds. The [keyword generics initiative] (effects initiative) will be
able to provide similar functionality; however, that is a much more in-depth
solution using parameterized optional constness. This feature should not
conflict with anything introduced as part of that proposal.

## Usage with `&dyn`

To be on the safe side, calling const functions from compile-time trait objects
is not allowed. This would look something like the below, using the same `trait
Foo` as above:

```rust
// Signature is OK and will compile (as it currently does)
const fn comptime_dyn_foo(object: &dyn Foo) {
// This call is not allowed for the time being
object.foo();
}
```

In theory, this behavior should be possible to support. However, it requires
more in-depth design than just tracking `const`ness through monomorphization; in
order to keep RFC suface area minimal, this is considered a future possibility.

Of course, the `const` function can still be called as a standard runtime
function:

```rust
fn runtime_dyn_foo(object: &dyn Foo) {
object.foo();
}
```


# Prior art

[prior-art]: #prior-art

The [const function RFC](https://rust-lang.github.io/rfcs/0911-const-fn.html)
provides a reference for why `const` functions in Rust are generally useful.

[`async-fn-in-trait`] is a similar case of making standard function effects
available within traits. In this case, `async` comes with a lot more nuance than
`const`, so implementation effort for this RFC will most likely be much lower.

# Unresolved questions

[unresolved-questions]: #unresolved-questions

None at this time.

# Future possibilities

[future-possibilities]: #future-possibilities

- As part of the work of the accepted but not yet implemented [`refined-impls`]
RFC, it may be possible to mark a function `const` in an implementation even
if the trait signature does not indicate `const`.
- Calling `const` functions through `&dyn` could be added, as in [Usage with
&dyn](#usage-with-dyn). This is likely blocked on having effects as function
parameters, that is:
```rust
// This works
type F = fn() -> u32;
type U = unsafe fn() -> u32;

// But this does not yet work
// type C = const fn() -> u32;
// type A = async fn() -> u32;
```
- The [keyword generics initiative] will add much more fine tuned control
than the basic mechanics in this RFC, allowing for optional const bounds
in a parametric way.

[keyword generics initiative]: https://github.com/rust-lang/keyword-generics-initiative
[`async-fn-in-trait`]: https://rust-lang.github.io/rfcs/3185-static-async-fn-in-trait.html
[`refined-impls`]: https://rust-lang.github.io/rfcs/3245-refined-impls.html