Skip to content

Commit 5948264

Browse files
committed
Inferred and opt-out const bounds
1 parent 237cdad commit 5948264

File tree

1 file changed

+134
-25
lines changed

1 file changed

+134
-25
lines changed

const-generic-const-fn-bounds.md

Lines changed: 134 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,11 @@ generic parameter type), because they are fully unconstrained.
2323
[guide-level-explanation]: #guide-level-explanation
2424

2525
You can call methods of generic parameters of a const function, because they are implicitly assumed to be
26-
`const fn`. For example, the `Add` trait declaration has an additional `const` before the trait name, so
27-
you can use it as a trait bound on your generic parameters:
26+
`const fn`. For example, the `Add` trait bound can be used to call `Add::add` or `+` on the arguments
27+
with that bound.
2828

2929
```rust
30-
const fn triple_add<T: const Add>(a: T, b: T, c: T) -> T {
30+
const fn triple_add<T: Add>(a: T, b: T, c: T) -> T {
3131
a + b + c
3232
}
3333
```
@@ -46,26 +46,23 @@ impl const Add for MyInt {
4646
}
4747
```
4848

49-
The const requirement is required on all bounds of the impl and its methods,
49+
The const requirement is inferred on all bounds of the impl and its methods,
5050
so in the following `H` is required to have a const impl of `Hasher`, so that
5151
methods on `state` are callable.
5252

5353
```rust
5454
impl const Hash for MyInt {
55-
const fn hash<H>(
55+
fn hash<H>(
5656
&self,
5757
state: &mut H,
5858
)
59-
where H: const Hasher
59+
where H: Hasher
6060
{
6161
state.write(&[self.0 as u8]);
6262
}
6363
}
6464
```
6565

66-
While these `const` keywords could be inferred (after all, they are required), requiring them is
67-
forward compatible to schemes in the future that allow more fine grained control.
68-
6966
## Drop
7067

7168
A notable use case of `impl const` is defining `Drop` impls. If you write
@@ -101,7 +98,7 @@ parameters are not const.
10198
E.g.
10299

103100
```rust
104-
impl<T: const Add> const Add for Foo<T> {
101+
impl<T: Add> const Add for Foo<T> {
105102
fn add(self, other: Self) -> Self {
106103
Foo(self.0 + other.0)
107104
}
@@ -118,7 +115,7 @@ a const context. E.g. the following function may be called as
118115
`add(String::from("foo"), String::from("bar"))` at runtime.
119116

120117
```rust
121-
const fn add<T: const Add>(a: T, b: T) -> T {
118+
const fn add<T: Add>(a: T, b: T) -> T {
122119
a + b
123120
}
124121
```
@@ -129,6 +126,28 @@ backwards compatibility.
129126
Changing an impl to only allow generic types which have a `const` impl for their bounds would break
130127
situations like the one described above.
131128

129+
## `?const` opt out
130+
131+
There is often desire to add bounds to a `const` function's generic arguments, without wanting to
132+
call any of the methods on those generic bounds. Prominent examples are `new` methods:
133+
134+
```rust
135+
struct Foo<T: Trait>(T);
136+
const fn new<T: Trait>(t: T) -> Foo<T> {
137+
Foo(t)
138+
}
139+
```
140+
141+
Unfortunately, with the given syntax in this RFC, one can now only call the `new` method if `T` has
142+
an `impl const Trait for T { ... }`. Thus an opt-out similar to `?Sized` can be used:
143+
144+
```rust
145+
struct Foo<T: Trait>(T);
146+
const fn new<T: ?const Trait>(t: T) -> Foo<T> {
147+
Foo(t)
148+
}
149+
```
150+
132151
# Reference-level explanation
133152
[reference-level-explanation]: #reference-level-explanation
134153

@@ -142,13 +161,12 @@ of `impl const` items.
142161

143162
## Implementation instructions
144163

145-
1. Add an `is_const` field to the AST's `TraitRef`
146-
2. Adjust the Parser to support `const` modifiers before trait bounds
147-
3. Add an `is_const` field to the HIR's `TraitRef`
148-
4. Adjust lowering to pass through the `is_const` field from AST to HIR
149-
5. Add a a check to `librustc_typeck/check/wfcheck.rs` ensuring that all generic bounds
150-
in an `impl const` block have the `in_const` flag set and all methods' `constness` field is
151-
`Const`.
164+
1. Add an `maybe_const` field to the AST's `TraitRef`
165+
2. Adjust the Parser to support `?const` modifiers before trait bounds
166+
3. Add an `maybe_const` field to the HIR's `TraitRef`
167+
4. Adjust lowering to pass through the `maybe_const` field from AST to HIR
168+
5. Add a a check to `librustc_typeck/check/wfcheck.rs` ensuring that no generic bounds
169+
in an `impl const` block have the `maybe_const` flag set
152170
6. Feature gate instead of ban `Predicate::Trait` other than `Sized` in
153171
`librustc_mir/transform/qualify_min_const_fn.rs`
154172
7. Remove the call in https://github.com/rust-lang/rust/blob/f8caa321c7c7214a6c5415e4b3694e65b4ff73a7/src/librustc_passes/ast_validation.rs#L306
@@ -189,14 +207,18 @@ can be applied to specific methods. E.g. `where <T as Add>::add: const` or somet
189207
the sort. This design is more complex than the current one and we'd probably want the
190208
current one as sugar anyway
191209

192-
## No explicit `const` bounds
210+
## Require `const` bounds everywhere
193211

194-
One could require no `const` on the bounds (e.g. `T: Trait`) and assume constness for all
195-
bounds. An opt-out via `T: ?const Trait` would then allow declaring bounds that cannot be
196-
used for calling methods. This design causes discrepancies with `const fn` pointers as
197-
arguments (where the constness would be needed, as normal function pointers already exist
198-
as the type of constants). Also it is not forward compatible to allowing `const` trait bounds
199-
on non-const functions
212+
One could require `const` on the bounds (e.g. `T: const Trait`) instead of assuming constness for all
213+
bounds. That design would not be forward compatible to allowing `const` trait bounds
214+
on non-const functions, e.g. in
215+
216+
```rust
217+
fn foo<T: const Bar>() -> i32 {
218+
const FOO: i32 = T::bar();
219+
FOO
220+
}
221+
```
200222

201223
## Infer all the things
202224

@@ -283,6 +305,93 @@ Impl specialization is still unstable. There should be a separate RFC for declar
283305
const impl blocks and specialization interact. For now one may not have both `default`
284306
and `const` modifiers on `impl` blocks.
285307

308+
## `const` trait methods
309+
310+
This RFC does not touch `trait` methods at all, all traits are defined as they would be defined
311+
without `const` functions existing. A future extension could allow
312+
313+
```rust
314+
trait Foo {
315+
const fn a() -> i32;
316+
fn b() -> i32;
317+
}
318+
```
319+
320+
Where all trait impls *must* provide a `const` function for `a`, allowing
321+
322+
```rust
323+
const fn foo<T: ?const Foo>() -> i32 {
324+
T::a()
325+
}
326+
```
327+
328+
even though the `?const` modifier explicitly opts out of constness.
329+
330+
## `?const` modifiers in trait methods
331+
332+
This RFC does not touch `trait` methods at all, all traits are defined as they would be defined
333+
without `const` functions existing. A future extension could allow
334+
335+
```rust
336+
trait Foo {
337+
fn a<T: ?const Bar>() -> i32;
338+
}
339+
```
340+
341+
which does not force `impl const Foo for Type` to now require passing a `T` with an `impl const Bar`
342+
to the `a` method.
343+
344+
## `const` function pointers
345+
346+
```rust
347+
const fn foo(f: fn() -> i32) -> i32 {
348+
f()
349+
}
350+
```
351+
352+
is currently illegal. While we can change the language to allow this feature, two questions make
353+
themselves known:
354+
355+
1. fn pointers in constants
356+
357+
```rust
358+
const F: fn() -> i32 = ...;
359+
```
360+
361+
is already legal in Rust today, even though the `F` doesn't need to be a `const` function.
362+
363+
2. Opt out bounds are ugly
364+
365+
I don't think it's either intuitive nor readable to write the following
366+
367+
```rust
368+
const fn foo(f: ?const fn() -> i32) -> i32 {
369+
// not allowed to call `f` here, because we can't guarantee that it points to a `const fn`
370+
}
371+
```
372+
373+
Thus it seems useful to prefix function pointers to `const` functions with `const`:
374+
375+
```rust
376+
const fn foo(f: const fn() -> i32) -> i32 {
377+
f()
378+
}
379+
const fn bar(f: fn() -> i32) -> i32 {
380+
f() // ERROR
381+
}
382+
```
383+
384+
This opens up the curious situation of `const` function pointers in non-const functions:
385+
386+
```rust
387+
fn foo(f: const fn() -> i32) -> i32 {
388+
f()
389+
}
390+
```
391+
392+
Which is useless except for ensuring some sense of "purity" of the function pointer ensuring that
393+
subsequent calls will only modify global state if passed in via arguments.
394+
286395
# Unresolved questions
287396
[unresolved-questions]: #unresolved-questions
288397

0 commit comments

Comments
 (0)