Skip to content

Commit d50c8ef

Browse files
committed
Clarify some things that came up in reviews
1 parent 0c59930 commit d50c8ef

File tree

1 file changed

+74
-2
lines changed

1 file changed

+74
-2
lines changed

text/0000-const-trait-impls.md

+74-2
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,8 @@ Impls can now rely on the default methods being const, too, and don't need to ov
132132
We may add an attribute later to allow you to mark individual trait methods as not-const so that when creating a const trait, one can
133133
add (defaulted or not) methods that cannot be used in const contexts.
134134

135+
It is possible to split up a trait into the const an non-const parts as discussed [here](#cant-have-const-methods-and-nonconst-methods-on-the-same-trait).
136+
135137
All default method bodies of const trait declarations are [const contexts](https://doc.rust-lang.org/reference/const_eval.html#const-context).
136138

137139
Note that on nightly the syntax is
@@ -211,6 +213,44 @@ const fn default<T: ~const Default>() -> T {
211213

212214
`~const` is derived from "approximately", meaning "conditionally" in this context, or specifically "const impl required if called in const context".
213215
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.
217+
218+
### Const fn
219+
220+
`const` fn have always been and will stay "always const" functions.
221+
222+
It may appear that a function is suddenly "not a const fn" if it gets passed a type that doesn't satisfy
223+
the constness of the corresponding trait bound. E.g.
224+
225+
```rust
226+
struct Foo;
227+
228+
impl Clone for Foo {
229+
fn clone(&self) -> Self {
230+
Foo
231+
}
232+
}
233+
234+
const fn bar<T: ~const Clone>(t: &T) -> T { t.clone() }
235+
const BAR: Foo = bar(Foo); // ERROR: `Foo`'s `Clone` impl is not for `const Clone`.
236+
```
237+
238+
But `bar` is still a `const` fn and you can call it from a const context, it will just fail some trait bounds. This is no different from
239+
240+
```rust
241+
const fn dup<T: Copy>(a: T) -> (T, T) {(a, a)}
242+
const FOO: (String, String) = dup(String::new());
243+
```
244+
245+
Here `dup` is always const fn, you'll just get a trait bound failure if the type you pass isn't `Copy`.
246+
247+
This may seem like language lawyering, but that's how the impl works and how we should be talking about it.
248+
249+
It's actually important for inference and method resolution in the nonconst world today.
250+
You first figure out which method you're calling, then you check its bounds.
251+
Otherwise it would at least seem like we'd have to allow some SFINAE or method overloading style things,
252+
which we definitely do not support and have historically rejected over and over again.
253+
214254

215255
### `~const Destruct` trait
216256

@@ -237,6 +277,19 @@ The `Destruct` trait is a bound for whether a type has drop glue. This is trival
237277
`~const Destruct` trait bounds are satsifed only if the type has a `const Drop` impl or all of the types of its components
238278
are `~const Destruct`.
239279

280+
While this means that it's a breaking change to add a type with a non-const `Drop` impl to a type,
281+
that's already true and nothing new:
282+
283+
```rust
284+
pub struct S {
285+
x: u8,
286+
y: Box<()>, // adding this field breaks code.
287+
}
288+
289+
const fn f(_: S) {}
290+
//~^ ERROR destructor of `S` cannot be evaluated at compile-time
291+
```
292+
240293
## Trivially enabled features
241294

242295
You can use `==` operators on most types from libstd from within const contexts.
@@ -554,6 +607,14 @@ Note that it may frequently be that such a trait should have been split even wit
554607

555608
It may seem tempting to use `const fn foo<T: const Trait>` to mean what in this RFC is `~const Trait`, and then add new syntax for bounds that allow using trait methods in const blocks.
556609

610+
Examples of possible always const syntax:
611+
612+
* `=const Trait`
613+
* `const const Trait` (lol)
614+
* `const(always) Trait` (`pub` like)
615+
* `const<true> Trait` (effect generic like)
616+
* `const! Trait`
617+
557618
## use `Trait<const>` or `Trait<bikeshed#effect: const>` instead of `const Trait`
558619

559620
To avoid new syntax before paths referring to traits, we could treat the constness as a generic parameter or an associated type.
@@ -608,6 +669,8 @@ for when a trait bound is only used for its associated types and consts.
608669
This requires a new `~const fn` syntax (sigils or syntax bikesheddable), as the existing `const fn` already has trait bounds that
609670
do not require const trait impls even if used in const contexts.
610671

672+
An example from libstd today is [the impl block of Vec::new](https://github.com/rust-lang/rust/blob/1ab85fbd7474e8ce84d5283548f21472860de3e2/library/alloc/src/vec/mod.rs#L406) which has an implicit `A: Allocator` bound from [the type definition](https://github.com/rust-lang/rust/blob/1ab85fbd7474e8ce84d5283548f21472860de3e2/library/alloc/src/vec/mod.rs#L397).
673+
611674
A full example how how things would look then
612675

613676
```rust
@@ -638,8 +701,17 @@ const fn foo<T: Foo>() {
638701
compiles today, and allows all types that implement `Foo`, irrespective of the constness of the impl.
639702
With the opt-out scheme that would still compile, but suddenly require callers to provide a const impl.
640703

641-
The safe default (and the one folks are used to for a few years now), is that trait bounds just work, you just
642-
can't call methods on them. To get more capabilities, you add more syntax. Thus the opt-out approach was not taken.
704+
The safe default (and the one folks are used to for a few years now on stable), is that trait bounds just work, you just
705+
can't call methods on them.
706+
This is both useful in
707+
708+
* nudging function authors to using the minimal necessary bounds to get their function
709+
body to compile and thus requiring as little as possible from their callers,
710+
* ensuring our implementation is correct by default.
711+
712+
The implementation correctness argument is partially due to our history with `?const` (see https://github.com/rust-lang/rust/issues/83452 for where we got it wrong and thus decided to stop using opt-out), and partially with our history with `?` bounds not being great either (https://github.com/rust-lang/rust/issues/135229, https://github.com/rust-lang/rust/pull/132209). An opt-in is much easier to make sound and keep sound.
713+
714+
To get more capabilities, you add more syntax. Thus the opt-out approach was not taken.
643715

644716
## Per-method constness instead of per-trait
645717

0 commit comments

Comments
 (0)