Skip to content

Commit 83f31f7

Browse files
committed
Address reviews
1 parent fe97e3f commit 83f31f7

File tree

1 file changed

+94
-20
lines changed

1 file changed

+94
-20
lines changed

text/0000-const-trait-impls.md

Lines changed: 94 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -151,12 +151,13 @@ Any item that can have trait bounds can also have `const Trait` bounds.
151151

152152
Examples:
153153

154-
* `T: const Trait`, requiring any type that `T` is instantiated with to have a const trait impl.
155-
* `dyn const Trait`, requiring any type that is unsized to this dyn trait to have a const trait impl.
156-
* These are not part of this RFC because they require `const fn` function pointers. See [the Future Possibilities section](#future-possibilities).
154+
* `T: const Trait`, requiring any type that `T` is instantiated with to have a const trait impl for `Trait`.
155+
* `dyn const Trait`, requiring any type that is unsized to this dyn trait to have a const trait impl for `Trait`.
156+
* These are not part of this RFC because they require `const` function pointers. See [the Future Possibilities section](#future-possibilities).
157157
* `impl const Trait` (in all positions).
158-
* These are not part of this RFC because they require `const fn` function pointers. See [the Future Possibilities section](#future-possibilities).
158+
* These are not part of this RFC because they require `const` function pointers. See [the Future Possibilities section](#future-possibilities).
159159
* `trait Foo: const Bar {}`, requiring every type that has an impl for `Foo` (even a non-const one), to also have a const trait impl for `Bar`.
160+
* `trait Foo { type Bar: const Trait; }`, requiring all the impls to provide a type for `Bar` that has a const trait impl for `Trait`
160161

161162
Such an impl allows you to use the type that is bound within a const block or any other const context, because we know that the type has a const trait impl and thus
162163
must be executable at compile time. The following function will invoke the `Default` impl of a type at compile time and store the result in a constant. Then it returns that constant instead of computing the value every time.
@@ -212,8 +213,9 @@ const fn default<T: ~const Default>() -> T {
212213
```
213214

214215
`~const` is derived from "approximately", meaning "conditionally" in this context, or specifically "const impl required if called in const context".
215-
It is the opposite of `?` (prexisting for `?Sized` bounds), which also means "conditionally", but from the other direction: `?const` (not proposed here, see the alternatives section for why it was rejected) would mean "no const impl required, even if called in const context".
216-
See [this alternatives section](#make-all-const-fn-arguments-const-trait-by-default-and-require-an-opt-out-const-trait) for an explanation of why we do not use a `?const` scheme.
216+
It is the opposite of `?` (prexisting for `?Sized` bounds), which also means "conditionally", but from the other direction: `?const`
217+
(not proposed here, see [this alternatives section](#make-all-const-fn-arguments-const-trait-by-default-and-require-an-opt-out-const-trait) for why it was rejected)
218+
would mean "no const impl required, even if called in const context".
217219

218220
### Const fn
219221

@@ -251,6 +253,33 @@ You first figure out which method you're calling, then you check its bounds.
251253
Otherwise it would at least seem like we'd have to allow some SFINAE or method overloading style things,
252254
which we definitely do not support and have historically rejected over and over again.
253255

256+
### conditionally const trait impls
257+
258+
`const` trait impls for generic types work similarly to generic `const fn`.
259+
Any `impl const Trait for Type` is allowed to have `~const` trait bounds.
260+
261+
```rust
262+
struct MyStruct<T>(T);
263+
264+
impl<T: ~const Add<Output = T>> const Add for MyStruct<T> {
265+
type Output = MyStruct<T>;
266+
fn add(self, other: MyStruct<T>) -> MyStruct<T> {
267+
MyStruct(self.0 + other.0)
268+
}
269+
}
270+
271+
impl<T> const Add for &MyStruct<T>
272+
where
273+
for<'a> &'a T: ~const Add<Output = T>,
274+
{
275+
type Output = MyStruct<T>;
276+
fn add(self, other: &MyStruct<T>) -> MyStruct<T> {
277+
MyStruct(&self.0 + &other.0)
278+
}
279+
}
280+
```
281+
282+
See [this playground](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=313a38ef5c36b2ddf489f74167c1ac8a) for an example that works on nightly today.
254283

255284
### `~const Destruct` trait
256285

@@ -339,15 +368,21 @@ These `const` or `~const` trait bounds desugar to normal trait bounds without mo
339368

340369
A much more detailed explanation can be found in https://hackmd.io/@compiler-errors/r12zoixg1l#What-now
341370

371+
372+
In contrast to other keywords like `unsafe` or `async` (that give you raw pointer derefs or `await` calls respectively),
373+
the `const` keyword on functions or blocks restricts what you can do within those functions or blocks.
374+
Thus the compiler historically used `host` as the internal inverse representation of `const` and `~const` bounds.
375+
342376
We generate a `ClauseKind::HostEffect` for every `const` or `~const` bound.
343377
To mirror how some effectful languages represent such effects,
344-
I'm going to use `<Type as Trait>::k#host` to allow setting whether the `host` effect is "const" (disabled) or "conditionally" (generic).
345-
This is not comparable with other associated bounds like type bounds or const bounds, as the values the associated host effect can
346-
take do neither have a usual hierarchy nor a concrete single value we can compare due to the following handling of those bounds:
378+
I'm going to use `<Type as Trait>::k#constness` to allow setting whether the `constness` effect is "const" (disabled) or "conditionally" (generic).
379+
This is not comparable with other associated bounds like type bounds or const bounds, as the values the associated constness effect can
380+
take do neither have a usual hierarchy of trait bounds or subtyping nor a concrete single value we can compare due to the following handling of those bounds:
347381

348-
* There is no "always" (enabled), as that is just the lack of a host effect, meaning no `<Type as Trait>::k#host` bound at all.
382+
* There is no "disabled", as that is just the lack of a constness effect, meaning no `<Type as Trait>::k#constness` bound at all.
349383
* In contrast to other effect systems, we do not track the effect as a true generic parameter in the type system,
350-
but instead just ignore all `Conditionally` bounds in host environments and treat them as `Const` in const environments.
384+
but instead explicitly convert all requirements of `Conditionally` bounds in always-const environments to `Const`.
385+
* in other words: calling a `const fn<T: ~const Trait>()` in a const item or const block requires proving that the type used for `T` is `const`, as `~const` can't refer to any conditionally const bound like it can within other const fns.
351386

352387
While this could be modelled with generic parameters in the type system, that:
353388

@@ -373,7 +408,7 @@ desugars to
373408
fn compile_time_default<T>() -> T
374409
where
375410
T: Default,
376-
<T as Default>::k#host = Const,
411+
<T as Default>::k#constness = Const,
377412
{
378413
const { T::default() }
379414
}
@@ -393,7 +428,7 @@ desugars to
393428
const fn default<T>() -> T
394429
where
395430
T: Default,
396-
<T as Default>::k#host = Conditionally,
431+
<T as Default>::k#constness = Conditionally,
397432
{
398433
T::default()
399434
}
@@ -441,6 +476,10 @@ previous rule "adding a new method is not a breaking change if it has a default
441476

442477
### `~const Destruct` super trait
443478

479+
The `Destruct` marker trait is used to name the previously unnameable drop glue that every type has.
480+
It has no methods, as drop glue is handled entirely by the compiler,
481+
but in theory drop glue could become something one can explicitly call without having to resort to extracting the drop glue function pointer from a `dyn Trait`.
482+
444483
Traits that have `self` (by ownership) methods, will almost always drop the `self` in these methods' bodies unless they are simple wrappers that just forward to the generic parameters' bounds.
445484

446485
The following never drops `T`, because it's the job of `<T as Add>` to handle dropping the values.
@@ -449,7 +488,7 @@ The following never drops `T`, because it's the job of `<T as Add>` to handle dr
449488
struct NewType<T>(T);
450489

451490
impl<T: ~const Add<Output = T>> const Add for NewType<T> {
452-
type Output = Self,
491+
type Output = Self;
453492
fn add(self, other: Self) -> Self::Output {
454493
NewType(self.0 + other.0)
455494
}
@@ -600,6 +639,30 @@ the traits to be merged again. That's churn we'd like to avoid.
600639

601640
Note that it may frequently be that such a trait should have been split even without constness being part of the picture.
602641

642+
Similarly one may want an always-const method on a trait of otherwise non-const methods:
643+
644+
```rust
645+
const trait InitFoo {
646+
fn init(i: i32) -> Self;
647+
}
648+
trait Foo: const InitFoo {
649+
const INIT: Self = Self::init(0);
650+
fn do_stuff(&mut self);
651+
}
652+
```
653+
654+
or even only offer `INIT` if `InitFoo` is `const`:
655+
656+
```rust
657+
const trait InitFoo: Sized {
658+
fn init(i: i32) -> Self;
659+
}
660+
trait Foo: InitFoo {
661+
const INIT: Self = Self::init(0) where Self: const InitFoo;
662+
fn do_stuff(&mut self);
663+
}
664+
```
665+
603666
# Alternatives
604667
[alternatives]: #alternatives
605668

@@ -809,14 +872,23 @@ We do not need to immediately allow using methods on generic parameters of const
809872
The following example could be made to work with just const traits and const trait impls.
810873

811874
```rust
875+
struct MyStruct(i32);
876+
877+
impl const PartialEq for MyStruct {
878+
fn eq(&self, other: &MyStruct) -> bool {
879+
self.0 == other.0
880+
}
881+
}
882+
812883
const fn foo() {
813-
let a = [1, 2, 3];
814-
let b = [1, 2, 4];
884+
let a = MyStruct(1);
885+
let b = MyStruct(2);
815886
if a == b {}
816887
}
817888
```
818889

819-
Things like `Option::map` could not be made const without const trait bounds, as they need to actually call the generic `FnOnce` argument.
890+
Things like `Option::map` or `PartialEq` for arrays/tuples could not be made const without const trait bounds,
891+
as they need to actually call the generic `FnOnce` argument or nested `PartialEq` impls.
820892

821893

822894
# Future possibilities
@@ -843,10 +915,12 @@ just call all of these `const` and only separate the `~const Trait` bounds from
843915
## `const fn()` pointers
844916

845917
Just like `const fn foo(x: impl ~const Trait) { x.method() }` and `const fn foo(x: &dyn ~const Trait) { x.method() }` we want to allow
846-
`const fn foo(f: const fn()) { f() }`.
918+
`const fn foo(f: ~const fn()) { f() }`.
847919

848-
There is nothing design-wise blocking function pointers and calling them, they mainly require implementation work and extending the
849-
compiler's internal type system representation of a function signature to include constness.
920+
These require changing the type system, making the constness of a function pointer part of the type.
921+
This in turn implies that a `const fn()` function pointer, a `~const fn()` function pointer and a `fn()` function pointer could have
922+
different `TypeId`s, which is something that requires more design and consideration to clarify whether supporting downcasting with `Any`
923+
or just supporting `TypeId` equality checks detecting constness is desirable.
850924

851925
## `const` closures
852926

0 commit comments

Comments
 (0)