From bb42eea76c4a5fea286c2dedeb06d1f886ae7f66 Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Fri, 5 Oct 2018 14:36:45 +0200 Subject: [PATCH 01/36] Const generic const fn bounds --- text/0000-const-generic-const-fn-bounds.md | 123 +++++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 text/0000-const-generic-const-fn-bounds.md diff --git a/text/0000-const-generic-const-fn-bounds.md b/text/0000-const-generic-const-fn-bounds.md new file mode 100644 index 00000000000..a0c9665db2f --- /dev/null +++ b/text/0000-const-generic-const-fn-bounds.md @@ -0,0 +1,123 @@ +- Feature Name: const_generic_const_fn_bounds +- Start Date: 2018-10-05 +- RFC PR: (leave this empty) +- Rust Issue: (leave this empty) + +# Summary +[summary]: #summary + +Allow `const impl`s for trait impls where all method impls are checked as const fn. + +Make it legal to declare trait bounds on generic parameters of `const fn` and allow +the body of the const fn to call methods on these generic parameters. + +# Motivation +[motivation]: #motivation + +Currently one can declare const fns with generic parameters, but one cannot add trait bounds to these +generic parameters. Thus one is not able to call methods on the generic parameters (or on objects of the +generic parameter type), because they are fully unconstrained. + +# Guide-level explanation +[guide-level-explanation]: #guide-level-explanation + +You can call call methods of generic parameters of `const fn`, because they are implicitly assumed to be +`const fn`. For example, even though the `Add` trait declaration does not contain any mention of `const`, +you can use it as a trait bound on your generic parameters: + +```rust +const fn triple_add(a: T, b: T, c: T) -> T { + a + b + c +} +``` + +The obligation is passed to the caller of your `triple_add` function to supply a type whose `Add` impl is fully +`const`. Since `Add` only has `add` as a method, in this case one only needs to ensure that the `add` method is +`const fn`. Instead of adding a `const` modifier to all methods of a trait impl, the modifier is added to the entire +`impl` block: + +```rust +struct MyInt(i8); +const impl Add for MyInt { + fn add(self, other: Self) -> Self { + MyInt(self.0, other.0) + } +} +``` + +The const requirement is propagated to all bounds of the impl or its methods, +so in the following `H` is required to have a const impl of `Hasher`, so that +methods on `state` are callable. + +```rust +const impl Hash for MyInt { + fn hash( + &self, + state: &mut H, + ) + where H: Hasher + { + state.write(&[self.0 as u8]); + } +} +``` + +# Reference-level explanation +[reference-level-explanation]: #reference-level-explanation + +This is the technical portion of the RFC. Explain the design in sufficient detail that: + +- Its interaction with other features is clear. +- It is reasonably clear how the feature would be implemented. +- Corner cases are dissected by example. + +The section should return to the examples given in the previous section, and explain more fully how the detailed proposal makes those examples work. + +# Drawbacks +[drawbacks]: #drawbacks + +One cannot add trait bounds to `const fn` without them automatically +requiring `const impl`s for all monomorphizations. Even if one does not +call any method on the generic parameter, the methods are still required to be constant. + +It is not a fully general design that supports every possible use case, +but only covers the most common cases. See also the alternatives. + +# Rationale and alternatives +[rationale-and-alternatives]: #rationale-and-alternatives + +## Effect system + +A fully powered effect system can allow us to do fine grained constness propagation +(or no propagation where undesirable). This is way out of scope in the near future +and this RFC is forward compatible to have its background impl be an effect system. + +## Fine grained `const` annotations + +One could annotate methods instead of impls, allowing just marking some method impls +as const fn. This would require some sort of "const bounds" in generic functions that +can be applied to specific methods. E.g. `where ::add: const` or something of +the sort. + +## Explicit `const` bounds + +One could require `T: const Trait` bounds to differentiate between bounds on which methods +can be called and bounds on which no methods can be called. This can backwards compatibly be +added as an opt-out via `T: ?const Trait` if the desire for such differences is strong. + +## Infer all the things + +We can just throw all this complexity out the door and allow calling any method on generic +parameters without an extra annotation `iff` that method satisfies `const fn`. So we'd still +annotate methods in trait impls, but we would not block calling a function on whether the +generic parameters fulfill some sort of constness rules. Instead we'd catch this during +const evaluation. + +This is strictly the most powerful and generic variant, but is an enormous backwards compatibility +hazard as changing a const fn's body to suddenly call a method that it did not before can break +users of the function. + +# Unresolved questions +[unresolved-questions]: #unresolved-questions + +None \ No newline at end of file From cb3f9f07b35d7ee78b8ac5f4a3c0261deb0a810f Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Tue, 5 Feb 2019 10:51:33 +0100 Subject: [PATCH 02/36] Update to rust-rfcs/const-eval repo version --- text/0000-const-generic-const-fn-bounds.md | 537 +++++++++++++++++++-- 1 file changed, 508 insertions(+), 29 deletions(-) diff --git a/text/0000-const-generic-const-fn-bounds.md b/text/0000-const-generic-const-fn-bounds.md index a0c9665db2f..69e2251ddf4 100644 --- a/text/0000-const-generic-const-fn-bounds.md +++ b/text/0000-const-generic-const-fn-bounds.md @@ -6,10 +6,11 @@ # Summary [summary]: #summary -Allow `const impl`s for trait impls where all method impls are checked as const fn. +Allow `impl const Trait` for trait impls where all method impls are checked as const fn. -Make it legal to declare trait bounds on generic parameters of `const fn` and allow -the body of the const fn to call methods on these generic parameters. +Make it legal to declare trait bounds on generic parameters of const functions and allow +the body of the const fn to call methods on the generic parameters that have a `const` modifier +on their bound. # Motivation [motivation]: #motivation @@ -21,9 +22,9 @@ generic parameter type), because they are fully unconstrained. # Guide-level explanation [guide-level-explanation]: #guide-level-explanation -You can call call methods of generic parameters of `const fn`, because they are implicitly assumed to be -`const fn`. For example, even though the `Add` trait declaration does not contain any mention of `const`, -you can use it as a trait bound on your generic parameters: +You can call methods of generic parameters of a const function, because they are implicitly assumed to be +`const fn`. For example, the `Add` trait bound can be used to call `Add::add` or `+` on the arguments +with that bound. ```rust const fn triple_add(a: T, b: T, c: T) -> T { @@ -33,24 +34,27 @@ const fn triple_add(a: T, b: T, c: T) -> T { The obligation is passed to the caller of your `triple_add` function to supply a type whose `Add` impl is fully `const`. Since `Add` only has `add` as a method, in this case one only needs to ensure that the `add` method is -`const fn`. Instead of adding a `const` modifier to all methods of a trait impl, the modifier is added to the entire +`const`. Instead of adding a `const` modifier to all methods of a trait impl, the modifier is added to the entire `impl` block: ```rust struct MyInt(i8); -const impl Add for MyInt { +impl const Add for MyInt { fn add(self, other: Self) -> Self { - MyInt(self.0, other.0) + MyInt(self.0 + other.0) } } ``` -The const requirement is propagated to all bounds of the impl or its methods, +You cannot implement both `const Add` and `Add` for any type, since the `const Add` +impl is used as a regular impl outside of const contexts. + +The const requirement is inferred on all bounds of the impl and its methods, so in the following `H` is required to have a const impl of `Hasher`, so that methods on `state` are callable. ```rust -const impl Hash for MyInt { +impl const Hash for MyInt { fn hash( &self, state: &mut H, @@ -62,26 +66,248 @@ const impl Hash for MyInt { } ``` +The same goes for associated types' bounds: all the bounds require `impl const`s for the type used +for the associated type: + +```rust +trait Foo { + type Bar: Add; +} +impl const Foo for A { + type Bar = B; // B must have an `impl const Add for B` +} +``` + +If an associated type has no bounds in the trait, there are no restrictions to what types may be used +for it. + +These rules for associated types exist to make this RFC forward compatible with adding const default bodies +for trait methods. These are further discussed in the "future work" section. + +## Generic bounds + +The above section skimmed over a few topics for brevity. First of all, `impl const` items can also +have generic parameters and thus bounds on these parameters, and these bounds follow the same rules +as bounds on generic parameters on `const` functions: all bounds can only be substituted with types +that have `impl const` items for all the bounds. Thus the `T` in the following `impl` requires that +when `MyType` is used in a const context, `T` needs to have an `impl const Add for Foo`. + +```rust +impl const Add for MyType { + /* some code here */ +} +const FOO: MyType = ...; +const BAR: MyType = FOO + FOO; // only legal because `u32: const Add` +``` + +Furthermore, if `MyType` is used outside a const context, there are no constness requirements on the +bounds for types substituted for `T`. + +## Drop + +A notable use case of `impl const` is defining `Drop` impls. +Since const evaluation has no side effects, there is no simple example that +showcases `const Drop` in any useful way. Instead we create a `Drop` impl that +has user visible side effects: + +```rust +let x = Cell::new(42); +SomeDropType(&x); +// x is now 41 + +struct SomeDropType<'a>(&'a Cell); +impl const Drop for SomeDropType { + fn drop(&mut self) { + self.0.set(self.0.get() - 1); + } +} +``` + +You are now allowed to actually let a value of `SomeDropType` get dropped within a constant +evaluation. This means + +```rust +(SomeDropType(&Cell::new(42)), 42).1 +``` + +is now allowed, because we can prove +that everything from the creation of the value to the destruction is const evaluable. + +Note that all fields of types with a `const Drop` impl must have `const Drop` impls, too, as the +compiler will automatically generate `Drop::drop` calls to the fields: + +```rust +struct Foo; +impl Drop for Foo { fn drop(&mut self) {} } +struct Bar(Foo); +impl const Drop for Foo { fn drop(&mut self) {} } // not allowed +``` + +## Runtime uses don't have `const` restrictions + +`impl const` blocks are treated as if the constness is a generic parameter +(see also effect systems in the alternatives). + +E.g. + +```rust +impl const Add for Foo { + fn add(self, other: Self) -> Self { + Foo(self.0 + other.0) + } +} +#[derive(Debug)] +struct Bar; +impl Add for Bar { + fn add(self, other: Self) -> Self { + println!("hello from the otter side: {:?}", other); + self + } +} +impl Neg for Bar { + fn neg(self) -> Self { + self + } +} +``` + +allows calling `Foo(Bar) + Foo(Bar)` even though that is most definitely not const, +because `Bar` only has an `impl Add for Bar` +and not an `impl const Add for Bar`. Expressed in some sort of effect system syntax (neither +effect syntax nor effect semantics are proposed by this RFC, the following is just for demonstration +purposes): + +```rust +impl const(c) Add for Foo { + const(c) fn add(self, other: Self) -> Self { + Foo(self.0 + other.0) + } +} +``` + +In this scheme on can see that if the `c` parameter is set to `const`, the `T` parameter requires a +`const Add` bound, and creates a `const Add` impl for `Foo` which then has a `const fn add` +method. On the other hand, if `c` is `?const`, we get a regular impl without any constness anywhere. +For regular impls one can still pass a `T` which has a `const Add` impl, but that won't +cause any constness for `Foo`. + +This goes in hand with the current scheme for const functions, which may also be called +at runtime with runtime arguments, but are checked for soundness as if they were called in +a const context. E.g. the following function may be called as +`add(Bar, Bar)` at runtime. + +```rust +const fn add>(a: T, b: U) -> T { + -a + b +} +``` + +Using the same effect syntax from above: + +```rust + const(c) fn add>(a: T, b: U) -> T { + -a + b +} +``` + +Here the value of `c` decides both whether the `add` function is `const` and whether its parameter +`T` has a `const Add` impl. Since both use the same `constness` variable, `T` is guaranteed to have +a `const Add` iff `add` is `const`. + +This feature could have been added in the future in a backwards compatible manner, but without it +the use of `const` impls is very restricted for the generic types of the standard library due to +backwards compatibility. +Changing an impl to only allow generic types which have a `const` impl for their bounds would break +situations like the one described above. + +## `?const` opt out + +There is often desire to add bounds to a `const` function's generic arguments, without wanting to +call any of the methods on those generic bounds. Prominent examples are `new` functions: + +```rust +struct Foo(T); +const fn new(t: T) -> Foo { + Foo(t) +} +``` + +Unfortunately, with the given syntax in this RFC, one can now only call the `new` function in a const +context if `T` has +an `impl const Trait for T { ... }`. Thus an opt-out similar to `?Sized` can be used: + +```rust +struct Foo(T); +const fn new(t: T) -> Foo { + Foo(t) +} +``` + +## `const` default method bodies + +Trait methods can have default bodies for methods that are used if the method is not mentioned +in an `impl`. This has several uses, most notably + +* reducing code repetition between impls that are all the same +* adding new methods is not a breaking change if they also have a default body + +In order to keep both advantages in the presence of `impl const`s, we need a way to declare the +method default body as being `const`. The exact syntax for doing so is left as an open question to +be decided during the implementation and following final comment period. For now one can add the +placeholder `#[default_method_body_is_const]` attribute to the method. + +```rust +trait Foo { + #[default_method_body_is_const] + fn bar() {} +} +``` + +While this conflicts with future work ideas like `const` trait methods or `const trait` declarations, +these features are unnecessary for full expressiveness as discussed in their respective sections. + # Reference-level explanation [reference-level-explanation]: #reference-level-explanation -This is the technical portion of the RFC. Explain the design in sufficient detail that: +The implementation of this RFC is (in contrast to some of its alternatives) mostly +changes around the syntax of the language (allowing `const` modifiers in a few places) +and ensuring that lowering to HIR and MIR keeps track of that. +The miri engine already fully supports calling methods on generic +bounds, there's just no way of declaring them. Checking methods for constness is already implemented +for inherent methods. The implementation will have to extend those checks to also run on methods +of `impl const` items. -- Its interaction with other features is clear. -- It is reasonably clear how the feature would be implemented. -- Corner cases are dissected by example. +## Implementation instructions -The section should return to the examples given in the previous section, and explain more fully how the detailed proposal makes those examples work. +1. Add an `maybe_const` field to the AST's `TraitRef` +2. Adjust the Parser to support `?const` modifiers before trait bounds +3. Add an `maybe_const` field to the HIR's `TraitRef` +4. Adjust lowering to pass through the `maybe_const` field from AST to HIR +5. Add a a check to `librustc_typeck/check/wfcheck.rs` ensuring that no generic bounds + in an `impl const` block have the `maybe_const` flag set +6. Feature gate instead of ban `Predicate::Trait` other than `Sized` in + `librustc_mir/transform/qualify_min_const_fn.rs` +7. Remove the call in https://github.com/rust-lang/rust/blob/f8caa321c7c7214a6c5415e4b3694e65b4ff73a7/src/librustc_passes/ast_validation.rs#L306 +8. Adjust the reference and the book to reflect these changes. + +## Const type theory + +This RFC was written after weighing practical issues against each other and finding the sweet spot +that supports most use cases, is sound and fairly intuitive to use. A different approach from a +type theoretical perspective started out with a much purer scheme, but, when exposed to the +constraints required, evolved to essentially the same scheme as this RFC. We thus feel confident +that this RFC is the minimal viable scheme for having bounds on generic parameters of const +functions. The discussion and evolution of the type theoretical scheme can be found +[here](https://github.com/rust-rfcs/const-eval/pull/8#issuecomment-452396020) and is only 12 posts +and a linked three page document long. It is left as an exercise to the reader to read the +discussion themselves. +A summary of the result of the discussion can be found at the bottom of [this blog post](https://varkor.github.io/blog/2019/01/11/const-types-traits-and-implementations-in-Rust.html) # Drawbacks [drawbacks]: #drawbacks -One cannot add trait bounds to `const fn` without them automatically -requiring `const impl`s for all monomorphizations. Even if one does not -call any method on the generic parameter, the methods are still required to be constant. - It is not a fully general design that supports every possible use case, -but only covers the most common cases. See also the alternatives. +but it covers the most common cases. See also the alternatives. # Rationale and alternatives [rationale-and-alternatives]: #rationale-and-alternatives @@ -89,7 +315,7 @@ but only covers the most common cases. See also the alternatives. ## Effect system A fully powered effect system can allow us to do fine grained constness propagation -(or no propagation where undesirable). This is way out of scope in the near future +(or no propagation where undesirable). This is out of scope in the near future and this RFC is forward compatible to have its background impl be an effect system. ## Fine grained `const` annotations @@ -97,13 +323,21 @@ and this RFC is forward compatible to have its background impl be an effect syst One could annotate methods instead of impls, allowing just marking some method impls as const fn. This would require some sort of "const bounds" in generic functions that can be applied to specific methods. E.g. `where ::add: const` or something of -the sort. +the sort. This design is more complex than the current one and we'd probably want the +current one as sugar anyway. -## Explicit `const` bounds +## Require `const` bounds everywhere -One could require `T: const Trait` bounds to differentiate between bounds on which methods -can be called and bounds on which no methods can be called. This can backwards compatibly be -added as an opt-out via `T: ?const Trait` if the desire for such differences is strong. +One could require `const` on the bounds (e.g. `T: const Trait`) instead of assuming constness for all +bounds. That design would not be forward compatible to allowing `const` trait bounds +on non-const functions, e.g. in: + +```rust +fn foo() -> i32 { + const FOO: i32 = T::bar(); + FOO +} +``` ## Infer all the things @@ -113,11 +347,256 @@ annotate methods in trait impls, but we would not block calling a function on wh generic parameters fulfill some sort of constness rules. Instead we'd catch this during const evaluation. -This is strictly the most powerful and generic variant, but is an enormous backwards compatibility +This is strictly the least restrictive and generic variant, but is a semver hazard as changing a const fn's body to suddenly call a method that it did not before can break users of the function. +# Future work + +This design is explicitly forward compatible to all future extensions the author could think +about. Notable mentions (see also the alternatives section): + +* an effect system with a "notconst" effect +* const trait bounds on non-const functions allowing the use of the generic parameter in + constant expressions in the body of the function or maybe even for array lenghts in the + signature of the function +* fine grained bounds for single methods and their bounds (e.g. stating that a single method + is const) + +It might also be desirable to make the automatic `Fn*` impls on function types and pointers `const`. +This change should probably go in hand with allowing `const fn` pointers on const functions +that support being called (in contrast to regular function pointers). + +## Deriving `impl const` + +```rust +#[derive(Clone)] +pub struct Foo(Bar); + +struct Bar; + +impl const Clone for Bar { + fn clone(&self) -> Self { Bar } +} +``` + +could theoretically have a scheme inferring `Foo`'s `Clone` impl to be `const`. If some time +later the `impl const Clone for Bar` (a private type) is changed to just `impl`, `Foo`'s `Clone` +impl would suddenly stop being `const`, without any visible change to the API. This should not +be allowed for the same reason as why we're not inferring `const` on functions: changes to private +things should not affect the constness of public things, because that is not compatible with semver. + +One possible solution is to require an explicit `const` in the derive: + +```rust +#[derive(const Clone)] +pub struct Foo(Bar); + +struct Bar; + +impl const Clone for Bar { + fn clone(&self) -> Self { Bar } +} +``` + +which would generate a `impl const Clone for Foo` block which would fail to compile if any of `Foo`'s +fields (so just `Bar` in this example) are not implementing `Clone` via `impl const`. The obligation is +now on the crate author to keep the public API semver compatible, but they can't accidentally fail to +uphold that obligation by changing private things. + +## RPIT (Return position impl trait) + +```rust +const fn foo() -> impl Bar { /* code here */ } +``` + +does not allow us to call any methods on the result of a call to `foo`, if we are in a +const context. It seems like a natural extension to this RFC to allow + +```rust +const fn foo() -> impl const Bar { /* code here */ } +``` + +which requires that the function only returns types with `impl const Bar` blocks. + +## Specialization + +Impl specialization is still unstable. There should be a separate RFC for declaring how +const impl blocks and specialization interact. For now one may not have both `default` +and `const` modifiers on `impl` blocks. + +## `const` trait methods + +This RFC does not touch `trait` methods at all, all traits are defined as they would be defined +without `const` functions existing. A future extension could allow + +```rust +trait Foo { + const fn a() -> i32; + fn b() -> i32; +} +``` + +Where all trait impls *must* provide a `const` function for `a`, allowing + +```rust +const fn foo() -> i32 { + T::a() +} +``` + +even though the `?const` modifier explicitly opts out of constness. + +The author of this RFC believes this feature to be unnecessary, since one can get the same effect +by splitting the trait into its const and nonconst parts: + +```rust +trait FooA { + fn a() -> i32; +} +trait FooB { + fn b() -> i32; +} +const fn foo() -> i32 { + T::a() +} +``` + +Impls of the two traits can then decide constness of either impl at their leasure. + +### `const` traits + +A further extension could be `const trait` declarations, which desugar to all methods being `const`: + +```rust +const trait V { + fn foo(C) -> D; + fn bar(E) -> F; +} +// ...desugars to... +trait V { + const fn foo(C) -> D; + const fn bar(E) -> F; +} +``` + +## `?const` modifiers in trait methods + +This RFC does not touch `trait` methods at all, all traits are defined as they would be defined +without `const` functions existing. A future extension could allow + +```rust +trait Foo { + fn a() -> i32; +} +``` + +which does not force `impl const Foo for Type` to now require passing a `T` with an `impl const Bar` +to the `a` method. + +## `const` function pointers + +```rust +const fn foo(f: fn() -> i32) -> i32 { + f() +} +``` + +is currently illegal. While we can change the language to allow this feature, two questions make +themselves known: + +1. fn pointers in constants + + ```rust + const F: fn() -> i32 = ...; + ``` + + is already legal in Rust today, even though the `F` doesn't need to be a `const` function. + +2. Opt out bounds might seem unintuitive? + + ```rust + const fn foo(f: ?const fn() -> i32) -> i32 { + // not allowed to call `f` here, because we can't guarantee that it points to a `const fn` + } + const fn foo(f: fn() -> i32) -> i32 { + f() + } + ``` + +Alternatively one can prefix function pointers to `const` functions with `const`: + +```rust +const fn foo(f: const fn() -> i32) -> i32 { + f() +} +const fn bar(f: fn() -> i32) -> i32 { + f() // ERROR +} +``` + +This opens up the curious situation of `const` function pointers in non-const functions: + +```rust +fn foo(f: const fn() -> i32) -> i32 { + f() +} +``` + +Which is useless except for ensuring some sense of "purity" of the function pointer ensuring that +subsequent calls will only modify global state if passed in via arguments. + +## explicit `const` bounds + +`const` on the bounds (e.g. `T: const Trait`) requires an `impl const Trait` for any types used to +replace `T`. This allows `const` trait bounds on any (even non-const) functions, e.g. in + +```rust +fn foo() -> i32 { + const FOO: i32 = T::bar(); + FOO +} +``` + +Which, once `const` items and array lengths inside of functions can make use of the generics of +the function, would allow the above function to actually exist. + +## `dyn Trait` + +A natural extension to this RFC is to allow + +```rust +const fn foo(bar: &dyn Trait) -> SomeType { + bar.some_method() +} +``` + +with an opt out via `?const` + +```rust +const fn foo(bar: &dyn ?const Trait) -> SomeType { + bar.some_method() // ERROR +} +``` + # Unresolved questions [unresolved-questions]: #unresolved-questions -None \ No newline at end of file +The syntax for specifying that a trait method's default body is `const` is left unspecified and uses +the `#[default_method_body_is_const]` attribute as the placeholder syntax. + +## Implied bounds + +Assuming we have implied bounds on functions or impl blocks, will the following compile? + +```rust +struct Foo { + t: T, + u: u32, +} + +/// T has implied bound `Add`, but is that `const Add` or `?const Add` or `!const Add`? +const fn foo(foo: Foo, bar: Foo) -> T { + foo.t + bar.t +} +``` From 9d22849434059dee5c05f06219c776562597a25b Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Tue, 5 Feb 2019 11:22:00 +0100 Subject: [PATCH 03/36] Elaborate on drawbacks --- text/0000-const-generic-const-fn-bounds.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/text/0000-const-generic-const-fn-bounds.md b/text/0000-const-generic-const-fn-bounds.md index 69e2251ddf4..076c15c9a13 100644 --- a/text/0000-const-generic-const-fn-bounds.md +++ b/text/0000-const-generic-const-fn-bounds.md @@ -306,8 +306,10 @@ A summary of the result of the discussion can be found at the bottom of [this bl # Drawbacks [drawbacks]: #drawbacks -It is not a fully general design that supports every possible use case, -but it covers the most common cases. See also the alternatives. +* It is not a fully general design that supports every possible use case, + but it covers the most common cases. See also the alternatives. +* It becomes a breaking change to add a new method to a trait, even if that method has a default + impl. One needs to provide a `const` default impl to not make the change a breaking change. # Rationale and alternatives [rationale-and-alternatives]: #rationale-and-alternatives From ea6f4ccff94e0eccf0495764dfdf170d741ec029 Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Tue, 5 Feb 2019 11:22:13 +0100 Subject: [PATCH 04/36] Make an example actually compile --- text/0000-const-generic-const-fn-bounds.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-const-generic-const-fn-bounds.md b/text/0000-const-generic-const-fn-bounds.md index 076c15c9a13..adb31e316db 100644 --- a/text/0000-const-generic-const-fn-bounds.md +++ b/text/0000-const-generic-const-fn-bounds.md @@ -27,7 +27,7 @@ You can call methods of generic parameters of a const function, because they are with that bound. ```rust -const fn triple_add(a: T, b: T, c: T) -> T { +const fn triple_add>(a: T, b: T, c: T) -> T { a + b + c } ``` From 66d8354ee3c80837a62ba71d09fe9280822cd7bb Mon Sep 17 00:00:00 2001 From: Joe ST Date: Wed, 6 Feb 2019 10:45:29 +0100 Subject: [PATCH 05/36] Drop the correct type Co-Authored-By: oli-obk --- text/0000-const-generic-const-fn-bounds.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-const-generic-const-fn-bounds.md b/text/0000-const-generic-const-fn-bounds.md index adb31e316db..93e8d753de3 100644 --- a/text/0000-const-generic-const-fn-bounds.md +++ b/text/0000-const-generic-const-fn-bounds.md @@ -140,7 +140,7 @@ compiler will automatically generate `Drop::drop` calls to the fields: struct Foo; impl Drop for Foo { fn drop(&mut self) {} } struct Bar(Foo); -impl const Drop for Foo { fn drop(&mut self) {} } // not allowed +impl const Drop for Bar { fn drop(&mut self) {} } // not allowed ``` ## Runtime uses don't have `const` restrictions From c0924f6233cbc93832c5595d9320f45c2c847fdf Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Mon, 11 Feb 2019 18:30:48 +0100 Subject: [PATCH 06/36] Nit --- text/0000-const-generic-const-fn-bounds.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/text/0000-const-generic-const-fn-bounds.md b/text/0000-const-generic-const-fn-bounds.md index 93e8d753de3..fffb1931676 100644 --- a/text/0000-const-generic-const-fn-bounds.md +++ b/text/0000-const-generic-const-fn-bounds.md @@ -133,7 +133,8 @@ evaluation. This means is now allowed, because we can prove that everything from the creation of the value to the destruction is const evaluable. -Note that all fields of types with a `const Drop` impl must have `const Drop` impls, too, as the +Note that all fields of types with a `const Drop` impl must either have `const Drop` impls or no +`Drop` impls, as the compiler will automatically generate `Drop::drop` calls to the fields: ```rust From a9f09d2f9d2cb475a9618983cfa76c59e8bae164 Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Sat, 16 Feb 2019 13:40:39 +0100 Subject: [PATCH 07/36] Address `const Drop` issues --- text/0000-const-generic-const-fn-bounds.md | 47 ++++++++++++++++++++-- 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/text/0000-const-generic-const-fn-bounds.md b/text/0000-const-generic-const-fn-bounds.md index fffb1931676..2f39df15d01 100644 --- a/text/0000-const-generic-const-fn-bounds.md +++ b/text/0000-const-generic-const-fn-bounds.md @@ -133,17 +133,56 @@ evaluation. This means is now allowed, because we can prove that everything from the creation of the value to the destruction is const evaluable. -Note that all fields of types with a `const Drop` impl must either have `const Drop` impls or no -`Drop` impls, as the -compiler will automatically generate `Drop::drop` calls to the fields: +Note that one can implement `const Drop` for structs with fields with just a regular `Drop` impl. +This will not allow using such ```rust struct Foo; impl Drop for Foo { fn drop(&mut self) {} } struct Bar(Foo); -impl const Drop for Bar { fn drop(&mut self) {} } // not allowed +impl const Drop for Bar { fn drop(&mut self) {} } +// cannot call with `T == Foo`, because of missing `const Drop` impl +fn foo(t: T) { + // Let t run out of scope and get dropped. + // Would not be ok if `T` is `Bar`, + // because the drop glue would drop `Bar`'s `Foo` field after the `Bar::drop` had been called. + // This function is therefor not accept by the compiler +} ``` +## const Drop in generic code + +`Drop` is special in Rust. You don't need to specify `T: Drop`, but `T::drop` will still be called +if an object of type `T` goes out of scope. This means there's an implicit assumption, that given +an arbitrary `T`, we might call `T::drop` if `T` has a drop impl. While we can specify +`T: const Drop` to allow calling `T::drop` in a `const fn`, this means we can't pass e.g. `u32` for +`T`, because `u32` has no `Drop` impl. Even types that definitely need dropping, but have no +explicit `Drop` impl (like `struct Foo(String);`) cannot be passed if `T` requires a `Drop` bound. + +To summarize, there are currently three ways to interact with `Drop`: + +* don't mention `Drop` in the parameter bounds (or mention `?Drop`, amounting to the same thing) + * can pass any type that fulfills the other bounds, but may never go out of scope +* mention `Drop` in the parameter bounds + * can only pass types with explicit `Drop` impls, still can't drop any values in the function +* mention `const Drop` in the parameter bounds + * can only pass types with explicit `const Drop` impls (so no `u32`) + +The language gets a new marker trait `ConstDrop` which is automatically implemented for + +1. any `Copy` type +2. any aggregate type with a `const Drop` impl consisting solely of elements of 1. 2. + +The body of a const function is allowed to generate drop glue for types that implement `ConstDrop`. + +While we could automatically implement `ConstDrop` for arbitrary types consisting only of other +`ConstDrop` types, this would make adding a `!ConstDrop` field to a type a breaking change. + +To reduce the confusion between `ConstDrop` and `const Drop` for users, +`impl const Drop for SomeType` automatically enforces `ConstDrop` for all fields of `SomeType` +similar to how `impl Drop for SomeType` is illegal if `SomeType` wasn't also defined with +the `Foo` bound on the `T`. + ## Runtime uses don't have `const` restrictions `impl const` blocks are treated as if the constness is a generic parameter From bff548372728dd14c2f0659a02f5ed2e2fddda91 Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Sat, 16 Feb 2019 13:41:01 +0100 Subject: [PATCH 08/36] Talk about `const A + B` operator precedence --- text/0000-const-generic-const-fn-bounds.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/text/0000-const-generic-const-fn-bounds.md b/text/0000-const-generic-const-fn-bounds.md index 2f39df15d01..17ae84c1770 100644 --- a/text/0000-const-generic-const-fn-bounds.md +++ b/text/0000-const-generic-const-fn-bounds.md @@ -317,6 +317,12 @@ bounds, there's just no way of declaring them. Checking methods for constness is for inherent methods. The implementation will have to extend those checks to also run on methods of `impl const` items. +## Precedence + +A bound with multiple traits only ever binds the `const` to the next trait, so `const Foo + Bar` +only means that one has a `const Foo` impl and a regular `Bar` impl. If both bounds are supposed to +be `const`, one needs to write `const Foo + const Bar`. More complex bounds might need parentheses. + ## Implementation instructions 1. Add an `maybe_const` field to the AST's `TraitRef` From 2301e8cdb3528e28947b4a7e207aa9f5e101d065 Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Sat, 16 Feb 2019 20:12:08 +0100 Subject: [PATCH 09/36] Apply suggestions from code review Co-Authored-By: oli-obk --- text/0000-const-generic-const-fn-bounds.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/text/0000-const-generic-const-fn-bounds.md b/text/0000-const-generic-const-fn-bounds.md index 17ae84c1770..0e10959222d 100644 --- a/text/0000-const-generic-const-fn-bounds.md +++ b/text/0000-const-generic-const-fn-bounds.md @@ -146,7 +146,7 @@ fn foo(t: T) { // Let t run out of scope and get dropped. // Would not be ok if `T` is `Bar`, // because the drop glue would drop `Bar`'s `Foo` field after the `Bar::drop` had been called. - // This function is therefor not accept by the compiler + // This function is therefore not accept by the compiler. } ``` @@ -157,7 +157,7 @@ if an object of type `T` goes out of scope. This means there's an implicit assum an arbitrary `T`, we might call `T::drop` if `T` has a drop impl. While we can specify `T: const Drop` to allow calling `T::drop` in a `const fn`, this means we can't pass e.g. `u32` for `T`, because `u32` has no `Drop` impl. Even types that definitely need dropping, but have no -explicit `Drop` impl (like `struct Foo(String);`) cannot be passed if `T` requires a `Drop` bound. +explicit `Drop` impl (like `struct Foo(String);`), cannot be passed if `T` requires a `Drop` bound. To summarize, there are currently three ways to interact with `Drop`: @@ -168,10 +168,10 @@ To summarize, there are currently three ways to interact with `Drop`: * mention `const Drop` in the parameter bounds * can only pass types with explicit `const Drop` impls (so no `u32`) -The language gets a new marker trait `ConstDrop` which is automatically implemented for +The language gets a new marker trait `ConstDrop` which is automatically implemented for: 1. any `Copy` type -2. any aggregate type with a `const Drop` impl consisting solely of elements of 1. 2. +2. any aggregate type with a `const Drop` impl consisting solely of elements of 1. and 2. The body of a const function is allowed to generate drop glue for types that implement `ConstDrop`. From 59036c28ea4afd63969c6403837404e8a807cdf5 Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Sat, 16 Feb 2019 20:17:36 +0100 Subject: [PATCH 10/36] Add missing word --- text/0000-const-generic-const-fn-bounds.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-const-generic-const-fn-bounds.md b/text/0000-const-generic-const-fn-bounds.md index 0e10959222d..3c007ed2c47 100644 --- a/text/0000-const-generic-const-fn-bounds.md +++ b/text/0000-const-generic-const-fn-bounds.md @@ -124,7 +124,7 @@ impl const Drop for SomeDropType { ``` You are now allowed to actually let a value of `SomeDropType` get dropped within a constant -evaluation. This means +evaluation. This means that ```rust (SomeDropType(&Cell::new(42)), 42).1 From 48936b2e18775a6dff1e40f3d14380c1751ff898 Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Sat, 16 Feb 2019 20:17:54 +0100 Subject: [PATCH 11/36] Arrays and tuples are const drop if all their fields are --- text/0000-const-generic-const-fn-bounds.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/text/0000-const-generic-const-fn-bounds.md b/text/0000-const-generic-const-fn-bounds.md index 3c007ed2c47..66f9078089d 100644 --- a/text/0000-const-generic-const-fn-bounds.md +++ b/text/0000-const-generic-const-fn-bounds.md @@ -171,7 +171,8 @@ To summarize, there are currently three ways to interact with `Drop`: The language gets a new marker trait `ConstDrop` which is automatically implemented for: 1. any `Copy` type -2. any aggregate type with a `const Drop` impl consisting solely of elements of 1. and 2. +2. any aggregate type with a `const Drop` impl consisting solely of elements of 1., 2. and 3. +3. arrays and tuples consisting solely of elements of 1. 2. and 3. The body of a const function is allowed to generate drop glue for types that implement `ConstDrop`. From 5c4d543b839bf6aa72c737475a6bf39603075070 Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Sat, 16 Feb 2019 20:21:59 +0100 Subject: [PATCH 12/36] Clear up `const Drop` vs `const Drop` fields --- text/0000-const-generic-const-fn-bounds.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/text/0000-const-generic-const-fn-bounds.md b/text/0000-const-generic-const-fn-bounds.md index 66f9078089d..d9377be80b0 100644 --- a/text/0000-const-generic-const-fn-bounds.md +++ b/text/0000-const-generic-const-fn-bounds.md @@ -133,14 +133,16 @@ evaluation. This means that is now allowed, because we can prove that everything from the creation of the value to the destruction is const evaluable. -Note that one can implement `const Drop` for structs with fields with just a regular `Drop` impl. -This will not allow using such +Note that one cannot implement `const Drop` for structs with fields with just a regular `Drop` impl. +While from a language perspective nothing speaks against that, this would be very surprising for +users. Additionally it would make `const Drop` pretty useless. This is explained in more detail in +the following subsection about generic code and `const Drop`. ```rust struct Foo; impl Drop for Foo { fn drop(&mut self) {} } struct Bar(Foo); -impl const Drop for Bar { fn drop(&mut self) {} } +impl const Drop for Bar { fn drop(&mut self) {} } // not ok // cannot call with `T == Foo`, because of missing `const Drop` impl fn foo(t: T) { // Let t run out of scope and get dropped. @@ -150,7 +152,7 @@ fn foo(t: T) { } ``` -## const Drop in generic code +### const Drop in generic code `Drop` is special in Rust. You don't need to specify `T: Drop`, but `T::drop` will still be called if an object of type `T` goes out of scope. This means there's an implicit assumption, that given From a4d849aafd55ab130ce199d42f765ec7daa60c62 Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Sat, 16 Feb 2019 21:34:26 +0100 Subject: [PATCH 13/36] Comma nits Co-Authored-By: oli-obk --- text/0000-const-generic-const-fn-bounds.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text/0000-const-generic-const-fn-bounds.md b/text/0000-const-generic-const-fn-bounds.md index d9377be80b0..1db742af4e1 100644 --- a/text/0000-const-generic-const-fn-bounds.md +++ b/text/0000-const-generic-const-fn-bounds.md @@ -173,8 +173,8 @@ To summarize, there are currently three ways to interact with `Drop`: The language gets a new marker trait `ConstDrop` which is automatically implemented for: 1. any `Copy` type -2. any aggregate type with a `const Drop` impl consisting solely of elements of 1., 2. and 3. -3. arrays and tuples consisting solely of elements of 1. 2. and 3. +2. any aggregate type with a `const Drop` impl consisting solely of elements of 1., 2., and 3. +3. arrays and tuples consisting solely of elements of 1., 2., and 3. The body of a const function is allowed to generate drop glue for types that implement `ConstDrop`. From 63d7d724113d9242b17566da131f756a0796c8af Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Sun, 17 Feb 2019 01:18:34 +0100 Subject: [PATCH 14/36] Remove mentions of `T: const Drop` --- text/0000-const-generic-const-fn-bounds.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/text/0000-const-generic-const-fn-bounds.md b/text/0000-const-generic-const-fn-bounds.md index 1db742af4e1..8c80b8f8f0d 100644 --- a/text/0000-const-generic-const-fn-bounds.md +++ b/text/0000-const-generic-const-fn-bounds.md @@ -144,7 +144,7 @@ impl Drop for Foo { fn drop(&mut self) {} } struct Bar(Foo); impl const Drop for Bar { fn drop(&mut self) {} } // not ok // cannot call with `T == Foo`, because of missing `const Drop` impl -fn foo(t: T) { +fn foo(t: T) { // Let t run out of scope and get dropped. // Would not be ok if `T` is `Bar`, // because the drop glue would drop `Bar`'s `Foo` field after the `Bar::drop` had been called. @@ -157,7 +157,7 @@ fn foo(t: T) { `Drop` is special in Rust. You don't need to specify `T: Drop`, but `T::drop` will still be called if an object of type `T` goes out of scope. This means there's an implicit assumption, that given an arbitrary `T`, we might call `T::drop` if `T` has a drop impl. While we can specify -`T: const Drop` to allow calling `T::drop` in a `const fn`, this means we can't pass e.g. `u32` for +`T: Drop` to allow calling `T::drop` in a `const fn`, this means we can't pass e.g. `u32` for `T`, because `u32` has no `Drop` impl. Even types that definitely need dropping, but have no explicit `Drop` impl (like `struct Foo(String);`), cannot be passed if `T` requires a `Drop` bound. @@ -165,9 +165,10 @@ To summarize, there are currently three ways to interact with `Drop`: * don't mention `Drop` in the parameter bounds (or mention `?Drop`, amounting to the same thing) * can pass any type that fulfills the other bounds, but may never go out of scope +* mention `?const Drop` in the parameter bounds + * can only pass types with explicit (const or not) `Drop` impls, + still can't drop any values inside the function * mention `Drop` in the parameter bounds - * can only pass types with explicit `Drop` impls, still can't drop any values in the function -* mention `const Drop` in the parameter bounds * can only pass types with explicit `const Drop` impls (so no `u32`) The language gets a new marker trait `ConstDrop` which is automatically implemented for: From 3c6e1d6fef2b633b3305e638c570e876beb118e2 Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Mon, 18 Feb 2019 13:48:01 +0100 Subject: [PATCH 15/36] Summarize the `Drop` discussion on the RFC PR --- text/0000-const-generic-const-fn-bounds.md | 42 ++++++++++++++++------ 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/text/0000-const-generic-const-fn-bounds.md b/text/0000-const-generic-const-fn-bounds.md index 8c80b8f8f0d..8f9798ad7bb 100644 --- a/text/0000-const-generic-const-fn-bounds.md +++ b/text/0000-const-generic-const-fn-bounds.md @@ -171,21 +171,21 @@ To summarize, there are currently three ways to interact with `Drop`: * mention `Drop` in the parameter bounds * can only pass types with explicit `const Drop` impls (so no `u32`) -The language gets a new marker trait `ConstDrop` which is automatically implemented for: +To resolve this, the language gets a new marker trait `ConstDrop` which is automatically implemented +for: -1. any `Copy` type -2. any aggregate type with a `const Drop` impl consisting solely of elements of 1., 2., and 3. -3. arrays and tuples consisting solely of elements of 1., 2., and 3. +* any `Copy` type +* arrays and tuples consisting solely of `ConstDrop` types -The body of a const function is allowed to generate drop glue for types that implement `ConstDrop`. +`ConstDrop` is similar to `Copy`: you can only implement it for your type, if all fields are +`ConstDrop`. While we could automatically implement `ConstDrop` for arbitrary types consisting only +of other `ConstDrop` types, this would make adding a `!ConstDrop` field to a type a breaking change. -While we could automatically implement `ConstDrop` for arbitrary types consisting only of other -`ConstDrop` types, this would make adding a `!ConstDrop` field to a type a breaking change. +The body of a const function is allowed to generate drop glue for types that implement `ConstDrop`. To reduce the confusion between `ConstDrop` and `const Drop` for users, -`impl const Drop for SomeType` automatically enforces `ConstDrop` for all fields of `SomeType` -similar to how `impl Drop for SomeType` is illegal if `SomeType` wasn't also defined with -the `Foo` bound on the `T`. +`impl const Drop for SomeType` requires a `ConstDrop` impl for `SomeType`, similar to how +implementing `Copy` requires you to also implement `Clone`. ## Runtime uses don't have `const` restrictions @@ -652,3 +652,25 @@ const fn foo(foo: Foo, bar: Foo) -> T { foo.t + bar.t } ``` + +## `ConstDrop` inference + +The design given in this RFC requires annotating a lot of generic parameters with `T: ConstDrop` to +make the code forward compatible with e.g. allowing dropping values of type `T`. Since removing a +trait bound other than `?Sized` is never a breaking change, we can remove the `ConstDrop` bound +without that being a breaking change for users. Adding a `ConstDrop` bound later would be a breaking +change though. + +As an alternative, we could automatically assume a `ConstDrop` bound for all `T` and require opt out +via `?ConstDrop`. This would be a breaking change, because + +```rust +const fn foo(t: T) -> T { t } +``` + +is legal on stable Rust. + +## `ConstDrop` is not a great name + +Bikeshed this name before stabilization. We don't want to end up with `T: const ConstDrop` bounds +in the future (see the "explicit `const` bounds" future extension to this RFC). From 0694122e27cdd0f6bfb928731552514108393b5f Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Tue, 12 Mar 2019 19:41:28 +0100 Subject: [PATCH 16/36] Start with a simpler example --- text/0000-const-generic-const-fn-bounds.md | 38 +++++++++++++--------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/text/0000-const-generic-const-fn-bounds.md b/text/0000-const-generic-const-fn-bounds.md index 8f9798ad7bb..cd83f727638 100644 --- a/text/0000-const-generic-const-fn-bounds.md +++ b/text/0000-const-generic-const-fn-bounds.md @@ -22,20 +22,9 @@ generic parameter type), because they are fully unconstrained. # Guide-level explanation [guide-level-explanation]: #guide-level-explanation -You can call methods of generic parameters of a const function, because they are implicitly assumed to be -`const fn`. For example, the `Add` trait bound can be used to call `Add::add` or `+` on the arguments -with that bound. - -```rust -const fn triple_add>(a: T, b: T, c: T) -> T { - a + b + c -} -``` - -The obligation is passed to the caller of your `triple_add` function to supply a type whose `Add` impl is fully -`const`. Since `Add` only has `add` as a method, in this case one only needs to ensure that the `add` method is -`const`. Instead of adding a `const` modifier to all methods of a trait impl, the modifier is added to the entire -`impl` block: +You can mark trait implementations as having only `const fn` methods. +Instead of adding a `const` modifier to all methods of a trait impl, the modifier is added to the trait +of the `impl` block: ```rust struct MyInt(i8); @@ -47,7 +36,26 @@ impl const Add for MyInt { ``` You cannot implement both `const Add` and `Add` for any type, since the `const Add` -impl is used as a regular impl outside of const contexts. +impl is used as a regular impl outside of const contexts. Inside a const context, you can now call +this method, even via its corresponding operator: + +```rust +const FOO: MyInt = MyInt(42).add(MyInt(33)); +const BAR: MyInt = MyInt(42) + MyInt(33); +``` + +You can also call methods of generic parameters of a const function, because they are implicitly assumed to be +`const fn`. For example, the `Add` trait bound can be used to call `Add::add` or `+` on the arguments +with that bound. + +```rust +const fn triple_add>(a: T, b: T, c: T) -> T { + a + b + c +} +``` + +The obligation is passed to the caller of your `triple_add` function to supply a type which has a +`const Add` impl. The const requirement is inferred on all bounds of the impl and its methods, so in the following `H` is required to have a const impl of `Hasher`, so that From 0de567db800a2294a5d25bf43f7479bbeac0070c Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Tue, 19 Mar 2019 13:58:08 +0100 Subject: [PATCH 17/36] Document `drop` glue trait --- text/0000-const-generic-const-fn-bounds.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/text/0000-const-generic-const-fn-bounds.md b/text/0000-const-generic-const-fn-bounds.md index cd83f727638..7d5bb9ee5b1 100644 --- a/text/0000-const-generic-const-fn-bounds.md +++ b/text/0000-const-generic-const-fn-bounds.md @@ -682,3 +682,8 @@ is legal on stable Rust. Bikeshed this name before stabilization. We don't want to end up with `T: const ConstDrop` bounds in the future (see the "explicit `const` bounds" future extension to this RFC). + +One suggestion is to have a lowercase trait `drop` that signifies the drop glue and not the `Drop` +trait. This would allow `T: drop` and `T: ?const drop` bounds signalling what's going on. +A `T: drop` bound on a non-const function would be useless as every type may get dropped +in non-const generic functions. From e03416e6dd75f7b4af507a43f2b05591f45962c7 Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Tue, 9 Apr 2019 19:59:15 +0200 Subject: [PATCH 18/36] Remove any mention of `Cell` --- text/0000-const-generic-const-fn-bounds.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/text/0000-const-generic-const-fn-bounds.md b/text/0000-const-generic-const-fn-bounds.md index 7d5bb9ee5b1..2b1359a636e 100644 --- a/text/0000-const-generic-const-fn-bounds.md +++ b/text/0000-const-generic-const-fn-bounds.md @@ -119,14 +119,14 @@ showcases `const Drop` in any useful way. Instead we create a `Drop` impl that has user visible side effects: ```rust -let x = Cell::new(42); -SomeDropType(&x); +let mut x = 42; +SomeDropType(&mut x); // x is now 41 -struct SomeDropType<'a>(&'a Cell); +struct SomeDropType<'a>(&'mut u32); impl const Drop for SomeDropType { fn drop(&mut self) { - self.0.set(self.0.get() - 1); + *self.0 -= 1; } } ``` @@ -135,7 +135,7 @@ You are now allowed to actually let a value of `SomeDropType` get dropped within evaluation. This means that ```rust -(SomeDropType(&Cell::new(42)), 42).1 +(SomeDropType(&mut 69), 42).1 ``` is now allowed, because we can prove From e19da15dd168f45fcb15c5ed133c741e1c9a3ea8 Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Tue, 9 Apr 2019 21:35:33 +0200 Subject: [PATCH 19/36] Add open syntax questions --- text/0000-const-generic-const-fn-bounds.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/text/0000-const-generic-const-fn-bounds.md b/text/0000-const-generic-const-fn-bounds.md index 2b1359a636e..14fe5fdb9e6 100644 --- a/text/0000-const-generic-const-fn-bounds.md +++ b/text/0000-const-generic-const-fn-bounds.md @@ -642,9 +642,21 @@ const fn foo(bar: &dyn ?const Trait) -> SomeType { # Unresolved questions [unresolved-questions]: #unresolved-questions +# Resolve syntax for making default method bodies `const` The syntax for specifying that a trait method's default body is `const` is left unspecified and uses the `#[default_method_body_is_const]` attribute as the placeholder syntax. +# Resolve keyword order of `impl const Trait` + +There are two possible ways to write the keywords `const` and `impl`: + +* `const impl Trait for Type` +* `impl const Trait for Type` + +The RFC favors the latter, as it mirrors the fact that trait bounds can be `const`. The constness +is not part of the `impl` block, but of how the trait is treated. This is in contrast to +`unsafe impl Trait for Type`, where the `unsafe` is irrelevant to users of the type. + ## Implied bounds Assuming we have implied bounds on functions or impl blocks, will the following compile? From 52fcff08fe6e0f8e3c7a0d41d4a8210531cd5220 Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Tue, 9 Apr 2019 21:35:50 +0200 Subject: [PATCH 20/36] Remove unactionable open question --- text/0000-const-generic-const-fn-bounds.md | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/text/0000-const-generic-const-fn-bounds.md b/text/0000-const-generic-const-fn-bounds.md index 14fe5fdb9e6..4d8ad968b07 100644 --- a/text/0000-const-generic-const-fn-bounds.md +++ b/text/0000-const-generic-const-fn-bounds.md @@ -673,23 +673,6 @@ const fn foo(foo: Foo, bar: Foo) -> T { } ``` -## `ConstDrop` inference - -The design given in this RFC requires annotating a lot of generic parameters with `T: ConstDrop` to -make the code forward compatible with e.g. allowing dropping values of type `T`. Since removing a -trait bound other than `?Sized` is never a breaking change, we can remove the `ConstDrop` bound -without that being a breaking change for users. Adding a `ConstDrop` bound later would be a breaking -change though. - -As an alternative, we could automatically assume a `ConstDrop` bound for all `T` and require opt out -via `?ConstDrop`. This would be a breaking change, because - -```rust -const fn foo(t: T) -> T { t } -``` - -is legal on stable Rust. - ## `ConstDrop` is not a great name Bikeshed this name before stabilization. We don't want to end up with `T: const ConstDrop` bounds From ab138dea4a8f8d4cf8a0a60db4d140bade3606cf Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Wed, 10 Apr 2019 10:59:59 +0200 Subject: [PATCH 21/36] Clarify all uses of "currently" --- text/0000-const-generic-const-fn-bounds.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/text/0000-const-generic-const-fn-bounds.md b/text/0000-const-generic-const-fn-bounds.md index 4d8ad968b07..c40b3698197 100644 --- a/text/0000-const-generic-const-fn-bounds.md +++ b/text/0000-const-generic-const-fn-bounds.md @@ -15,7 +15,7 @@ on their bound. # Motivation [motivation]: #motivation -Currently one can declare const fns with generic parameters, but one cannot add trait bounds to these +Without this RFC one can declare const fns with generic parameters, but one cannot add trait bounds to these generic parameters. Thus one is not able to call methods on the generic parameters (or on objects of the generic parameter type), because they are fully unconstrained. @@ -169,7 +169,7 @@ an arbitrary `T`, we might call `T::drop` if `T` has a drop impl. While we can s `T`, because `u32` has no `Drop` impl. Even types that definitely need dropping, but have no explicit `Drop` impl (like `struct Foo(String);`), cannot be passed if `T` requires a `Drop` bound. -To summarize, there are currently three ways to interact with `Drop`: +To summarize, up to this point in the RFC there are three ways to interact with `Drop`: * don't mention `Drop` in the parameter bounds (or mention `?Drop`, amounting to the same thing) * can pass any type that fulfills the other bounds, but may never go out of scope @@ -562,8 +562,8 @@ const fn foo(f: fn() -> i32) -> i32 { } ``` -is currently illegal. While we can change the language to allow this feature, two questions make -themselves known: +is illegal before and with this RFC. While we can change the language to allow this feature, two +questions make themselves known: 1. fn pointers in constants From 0929ed99c519951a4b6d1969ca4e4f8226a4dbae Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Wed, 10 Apr 2019 11:01:29 +0200 Subject: [PATCH 22/36] `?const Drop` is useless --- text/0000-const-generic-const-fn-bounds.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-const-generic-const-fn-bounds.md b/text/0000-const-generic-const-fn-bounds.md index c40b3698197..b1fe6098f1d 100644 --- a/text/0000-const-generic-const-fn-bounds.md +++ b/text/0000-const-generic-const-fn-bounds.md @@ -175,7 +175,7 @@ To summarize, up to this point in the RFC there are three ways to interact with * can pass any type that fulfills the other bounds, but may never go out of scope * mention `?const Drop` in the parameter bounds * can only pass types with explicit (const or not) `Drop` impls, - still can't drop any values inside the function + still can't drop any values inside the function (making this a useless bound) * mention `Drop` in the parameter bounds * can only pass types with explicit `const Drop` impls (so no `u32`) From 1b5feefe1e52ae1252bc7c6ee257dff41828a35f Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Wed, 10 Apr 2019 11:03:06 +0200 Subject: [PATCH 23/36] `ConstDrop` is not similar to `Copy` --- text/0000-const-generic-const-fn-bounds.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/text/0000-const-generic-const-fn-bounds.md b/text/0000-const-generic-const-fn-bounds.md index b1fe6098f1d..690430a29b6 100644 --- a/text/0000-const-generic-const-fn-bounds.md +++ b/text/0000-const-generic-const-fn-bounds.md @@ -192,8 +192,7 @@ of other `ConstDrop` types, this would make adding a `!ConstDrop` field to a typ The body of a const function is allowed to generate drop glue for types that implement `ConstDrop`. To reduce the confusion between `ConstDrop` and `const Drop` for users, -`impl const Drop for SomeType` requires a `ConstDrop` impl for `SomeType`, similar to how -implementing `Copy` requires you to also implement `Clone`. +`impl const Drop for SomeType` requires a `ConstDrop` impl for `SomeType`. ## Runtime uses don't have `const` restrictions From 93de85261d298ef1fde7a93d1f11ce714863a523 Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Wed, 10 Apr 2019 11:49:41 +0200 Subject: [PATCH 24/36] Maybe we don't need `ConstDrop` after all --- text/0000-const-generic-const-fn-bounds.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/text/0000-const-generic-const-fn-bounds.md b/text/0000-const-generic-const-fn-bounds.md index 690430a29b6..0601050079f 100644 --- a/text/0000-const-generic-const-fn-bounds.md +++ b/text/0000-const-generic-const-fn-bounds.md @@ -672,6 +672,13 @@ const fn foo(foo: Foo, bar: Foo) -> T { } ``` +## Extend the semantics of `T: Drop` bounds? + +Instead of using the `ConstDrop` workaround, we could extend `T: Drop` (in regular functions) to +also accept `i32` and other non-`Drop` types. In `const fn` this would mean you'd need to pass +either a non-`Drop` type or a type with a `const Drop` impl. Before stabilization we should +experiment with either design and discuss the effects this has on API design. + ## `ConstDrop` is not a great name Bikeshed this name before stabilization. We don't want to end up with `T: const ConstDrop` bounds From 8fa3c934c4e36c311ff4d7da0e123e85d6a57b37 Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Wed, 10 Apr 2019 13:09:33 +0200 Subject: [PATCH 25/36] correct indentation --- text/0000-const-generic-const-fn-bounds.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text/0000-const-generic-const-fn-bounds.md b/text/0000-const-generic-const-fn-bounds.md index 0601050079f..b3d7f635cdc 100644 --- a/text/0000-const-generic-const-fn-bounds.md +++ b/text/0000-const-generic-const-fn-bounds.md @@ -641,11 +641,11 @@ const fn foo(bar: &dyn ?const Trait) -> SomeType { # Unresolved questions [unresolved-questions]: #unresolved-questions -# Resolve syntax for making default method bodies `const` +## Resolve syntax for making default method bodies `const` The syntax for specifying that a trait method's default body is `const` is left unspecified and uses the `#[default_method_body_is_const]` attribute as the placeholder syntax. -# Resolve keyword order of `impl const Trait` +## Resolve keyword order of `impl const Trait` There are two possible ways to write the keywords `const` and `impl`: From 495cef68a49e115e279ae62f921e4cf87304463d Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Wed, 10 Apr 2019 14:21:20 +0200 Subject: [PATCH 26/36] Remove `ConstDrop` in favour of extending `const Drop` --- text/0000-const-generic-const-fn-bounds.md | 45 ++-------------------- 1 file changed, 4 insertions(+), 41 deletions(-) diff --git a/text/0000-const-generic-const-fn-bounds.md b/text/0000-const-generic-const-fn-bounds.md index b3d7f635cdc..e06546aac17 100644 --- a/text/0000-const-generic-const-fn-bounds.md +++ b/text/0000-const-generic-const-fn-bounds.md @@ -169,30 +169,10 @@ an arbitrary `T`, we might call `T::drop` if `T` has a drop impl. While we can s `T`, because `u32` has no `Drop` impl. Even types that definitely need dropping, but have no explicit `Drop` impl (like `struct Foo(String);`), cannot be passed if `T` requires a `Drop` bound. -To summarize, up to this point in the RFC there are three ways to interact with `Drop`: - -* don't mention `Drop` in the parameter bounds (or mention `?Drop`, amounting to the same thing) - * can pass any type that fulfills the other bounds, but may never go out of scope -* mention `?const Drop` in the parameter bounds - * can only pass types with explicit (const or not) `Drop` impls, - still can't drop any values inside the function (making this a useless bound) -* mention `Drop` in the parameter bounds - * can only pass types with explicit `const Drop` impls (so no `u32`) - -To resolve this, the language gets a new marker trait `ConstDrop` which is automatically implemented -for: - -* any `Copy` type -* arrays and tuples consisting solely of `ConstDrop` types - -`ConstDrop` is similar to `Copy`: you can only implement it for your type, if all fields are -`ConstDrop`. While we could automatically implement `ConstDrop` for arbitrary types consisting only -of other `ConstDrop` types, this would make adding a `!ConstDrop` field to a type a breaking change. - -The body of a const function is allowed to generate drop glue for types that implement `ConstDrop`. - -To reduce the confusion between `ConstDrop` and `const Drop` for users, -`impl const Drop for SomeType` requires a `ConstDrop` impl for `SomeType`. +To be able to know that a `T` can be dropped in a `const fn`, this RFC proposes to make `T: Drop` +be a valid bound for any `T`, even types which have no `Drop` impl. In non-const functions this +would make no difference, but `const fn` adding such a bound would allow dropping values of type +`T` inside the const function. ## Runtime uses don't have `const` restrictions @@ -671,20 +651,3 @@ const fn foo(foo: Foo, bar: Foo) -> T { foo.t + bar.t } ``` - -## Extend the semantics of `T: Drop` bounds? - -Instead of using the `ConstDrop` workaround, we could extend `T: Drop` (in regular functions) to -also accept `i32` and other non-`Drop` types. In `const fn` this would mean you'd need to pass -either a non-`Drop` type or a type with a `const Drop` impl. Before stabilization we should -experiment with either design and discuss the effects this has on API design. - -## `ConstDrop` is not a great name - -Bikeshed this name before stabilization. We don't want to end up with `T: const ConstDrop` bounds -in the future (see the "explicit `const` bounds" future extension to this RFC). - -One suggestion is to have a lowercase trait `drop` that signifies the drop glue and not the `Drop` -trait. This would allow `T: drop` and `T: ?const drop` bounds signalling what's going on. -A `T: drop` bound on a non-const function would be useless as every type may get dropped -in non-const generic functions. From 6de0a777844a1f0da2744a57cbae3f2d356d1747 Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Thu, 11 Apr 2019 10:31:34 +0200 Subject: [PATCH 27/36] Explain the issues with inferring `const Drop` --- text/0000-const-generic-const-fn-bounds.md | 51 +++++++++++++++------- 1 file changed, 36 insertions(+), 15 deletions(-) diff --git a/text/0000-const-generic-const-fn-bounds.md b/text/0000-const-generic-const-fn-bounds.md index e06546aac17..6be9576dee9 100644 --- a/text/0000-const-generic-const-fn-bounds.md +++ b/text/0000-const-generic-const-fn-bounds.md @@ -141,6 +141,32 @@ evaluation. This means that is now allowed, because we can prove that everything from the creation of the value to the destruction is const evaluable. +### const Drop in generic code + +`Drop` is special in Rust. You don't need to specify `T: Drop`, but `T::drop` will still be called +if an object of type `T` goes out of scope. This means there's an implicit assumption, that given +an arbitrary `T`, we might call `T::drop` if `T` has a drop impl. While we can specify +`T: Drop` to allow calling `T::drop` in a `const fn`, this means we can't pass e.g. `u32` for +`T`, because `u32` has no `Drop` impl. Even types that definitely need dropping, but have no +explicit `Drop` impl (like `struct Foo(String);`), cannot be passed if `T` requires a `Drop` bound. + +To be able to know that a `T` can be dropped in a `const fn`, this RFC proposes to make `T: Drop` +be a valid bound for any `T`, even types which have no `Drop` impl. In non-const functions this +would make no difference, but `const fn` adding such a bound would allow dropping values of type +`T` inside the const function. Additionally it would forbid calling a `const fn` with a `T: Drop` +bound with types that have non-const `Drop` impls (or have a field that has a non-const `Drop` impl). + +```rust +struct Foo; +impl Drop for Foo { fn drop(&mut self) {} } +struct Bar; +impl const Drop for Bar { fn drop(&mut self) {} } +struct Boo; +// cannot call with `T == Foo`, because of missing `const Drop` impl +// `Bar` and `Boo` are ok +const fn foo(t: T) {} +``` + Note that one cannot implement `const Drop` for structs with fields with just a regular `Drop` impl. While from a language perspective nothing speaks against that, this would be very surprising for users. Additionally it would make `const Drop` pretty useless. This is explained in more detail in @@ -152,7 +178,7 @@ impl Drop for Foo { fn drop(&mut self) {} } struct Bar(Foo); impl const Drop for Bar { fn drop(&mut self) {} } // not ok // cannot call with `T == Foo`, because of missing `const Drop` impl -fn foo(t: T) { +const fn foo(t: T) { // Let t run out of scope and get dropped. // Would not be ok if `T` is `Bar`, // because the drop glue would drop `Bar`'s `Foo` field after the `Bar::drop` had been called. @@ -160,20 +186,6 @@ fn foo(t: T) { } ``` -### const Drop in generic code - -`Drop` is special in Rust. You don't need to specify `T: Drop`, but `T::drop` will still be called -if an object of type `T` goes out of scope. This means there's an implicit assumption, that given -an arbitrary `T`, we might call `T::drop` if `T` has a drop impl. While we can specify -`T: Drop` to allow calling `T::drop` in a `const fn`, this means we can't pass e.g. `u32` for -`T`, because `u32` has no `Drop` impl. Even types that definitely need dropping, but have no -explicit `Drop` impl (like `struct Foo(String);`), cannot be passed if `T` requires a `Drop` bound. - -To be able to know that a `T` can be dropped in a `const fn`, this RFC proposes to make `T: Drop` -be a valid bound for any `T`, even types which have no `Drop` impl. In non-const functions this -would make no difference, but `const fn` adding such a bound would allow dropping values of type -`T` inside the const function. - ## Runtime uses don't have `const` restrictions `impl const` blocks are treated as if the constness is a generic parameter @@ -347,10 +359,19 @@ A summary of the result of the discussion can be found at the bottom of [this bl but it covers the most common cases. See also the alternatives. * It becomes a breaking change to add a new method to a trait, even if that method has a default impl. One needs to provide a `const` default impl to not make the change a breaking change. +* It becomes a breaking change to add a field (even a private one) that has a `Drop` impl which is + not `const Drop` (or which has such a field). # Rationale and alternatives [rationale-and-alternatives]: #rationale-and-alternatives +## `ConstDrop` trait to opt into const-droppability + +Right now it is a breaking change to add a field (even a private one) that has a non-const `Drop` +impl. This makes `const Drop` a marker trait similar to `Send` and `Sync`. Alternatively we can +introduce an explicit `ConstDrop` (name bikesheddable) trait, that needs to be implemented for all +types, even `Copy` types. Users would need to add `T: ConstDrop` bounds instead of `T: Drop` bounds. + ## Effect system A fully powered effect system can allow us to do fine grained constness propagation From 2637a08d16b69b8ceacdc49b7ba3fd6f574e1720 Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Thu, 11 Apr 2019 11:00:48 +0200 Subject: [PATCH 28/36] Remove invalid section reference --- text/0000-const-generic-const-fn-bounds.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/text/0000-const-generic-const-fn-bounds.md b/text/0000-const-generic-const-fn-bounds.md index 6be9576dee9..2743a1a8d09 100644 --- a/text/0000-const-generic-const-fn-bounds.md +++ b/text/0000-const-generic-const-fn-bounds.md @@ -169,8 +169,7 @@ const fn foo(t: T) {} Note that one cannot implement `const Drop` for structs with fields with just a regular `Drop` impl. While from a language perspective nothing speaks against that, this would be very surprising for -users. Additionally it would make `const Drop` pretty useless. This is explained in more detail in -the following subsection about generic code and `const Drop`. +users. Additionally it would make `const Drop` pretty useless. ```rust struct Foo; From cc1e8bb56566a0774e4cf2ea79f1fd74e3b2abc6 Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Mon, 15 Apr 2019 15:01:40 +0200 Subject: [PATCH 29/36] Mirror some examples in effect syntax --- text/0000-const-generic-const-fn-bounds.md | 43 ++++++++++++++++++++-- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/text/0000-const-generic-const-fn-bounds.md b/text/0000-const-generic-const-fn-bounds.md index 2743a1a8d09..895c1489a27 100644 --- a/text/0000-const-generic-const-fn-bounds.md +++ b/text/0000-const-generic-const-fn-bounds.md @@ -274,6 +274,15 @@ const fn new(t: T) -> Foo { } ``` +
Click here for effect system syntax description +```rust +struct Foo(T); + const(c) fn new(t: T) -> Foo { + Foo(t) +} +``` +
+ Unfortunately, with the given syntax in this RFC, one can now only call the `new` function in a const context if `T` has an `impl const Trait for T { ... }`. Thus an opt-out similar to `?Sized` can be used: @@ -285,6 +294,16 @@ const fn new(t: T) -> Foo { } ``` +
Click here for effect system syntax description +```rust +struct Foo(T); +// note the lack of `const(c)` before `Trait` + const(c) fn new(t: T) -> Foo { + Foo(t) +} +``` +
+ ## `const` default method bodies Trait methods can have default bodies for methods that are used if the method is not mentioned @@ -305,8 +324,8 @@ trait Foo { } ``` -While this conflicts with future work ideas like `const` trait methods or `const trait` declarations, -these features are unnecessary for full expressiveness as discussed in their respective sections. +While we could use `const fn bar() {}` as a syntax, that would conflict +with future work ideas like `const` trait methods or `const trait` declarations. # Reference-level explanation [reference-level-explanation]: #reference-level-explanation @@ -666,8 +685,26 @@ struct Foo { u: u32, } -/// T has implied bound `Add`, but is that `const Add` or `?const Add` or `!const Add`? +/// T has implied bound `Add`, but is that `const Add` or `?const Add` const fn foo(foo: Foo, bar: Foo) -> T { foo.t + bar.t } ``` + +In our exemplary effect syntax would need to add an effect +to struct definitions, too. + +```rust +struct Foo { + t: T, + u: u32, +} +// const Add + const(c) fn foo(foo: Foo, bar: Foo) -> T { + foo.t + bar.t +} +// ?const Add + const(c) fn foo(foo: Foo, bar: Foo) -> T { + foo.t + bar.t // error +} +``` From dabdff5d84aa5891f25bfc5c362e266d731858c7 Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Mon, 15 Apr 2019 15:18:02 +0200 Subject: [PATCH 30/36] Crosslink between related sections in the RFC --- text/0000-const-generic-const-fn-bounds.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/text/0000-const-generic-const-fn-bounds.md b/text/0000-const-generic-const-fn-bounds.md index 895c1489a27..3bc12168832 100644 --- a/text/0000-const-generic-const-fn-bounds.md +++ b/text/0000-const-generic-const-fn-bounds.md @@ -417,6 +417,8 @@ fn foo() -> i32 { } ``` +See also the [explicit `const` bounds](#explicit_const) extension to this RFC. + ## Infer all the things We can just throw all this complexity out the door and allow calling any method on generic @@ -625,6 +627,7 @@ Which is useless except for ensuring some sense of "purity" of the function poin subsequent calls will only modify global state if passed in via arguments. ## explicit `const` bounds +[explicit_const]: #explicit-const `const` on the bounds (e.g. `T: const Trait`) requires an `impl const Trait` for any types used to replace `T`. This allows `const` trait bounds on any (even non-const) functions, e.g. in From 68721d8c37d0822add6c1e28c9931c14ddbef49d Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Tue, 16 Apr 2019 14:45:17 +0200 Subject: [PATCH 31/36] Motivate `?const Trait` --- text/0000-const-generic-const-fn-bounds.md | 34 ++++++++++++++++++++-- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/text/0000-const-generic-const-fn-bounds.md b/text/0000-const-generic-const-fn-bounds.md index 3bc12168832..6776953f40c 100644 --- a/text/0000-const-generic-const-fn-bounds.md +++ b/text/0000-const-generic-const-fn-bounds.md @@ -229,7 +229,8 @@ impl const(c) Add for Foo { In this scheme on can see that if the `c` parameter is set to `const`, the `T` parameter requires a `const Add` bound, and creates a `const Add` impl for `Foo` which then has a `const fn add` -method. On the other hand, if `c` is `?const`, we get a regular impl without any constness anywhere. +method. On the other hand, if `c` is "may or may not be `const`", we get a regular impl without any +constness anywhere. For regular impls one can still pass a `T` which has a `const Add` impl, but that won't cause any constness for `Foo`. @@ -264,6 +265,8 @@ situations like the one described above. ## `?const` opt out +### Motivation + There is often desire to add bounds to a `const` function's generic arguments, without wanting to call any of the methods on those generic bounds. Prominent examples are `new` functions: @@ -284,8 +287,33 @@ struct Foo(T); Unfortunately, with the given syntax in this RFC, one can now only call the `new` function in a const -context if `T` has -an `impl const Trait for T { ... }`. Thus an opt-out similar to `?Sized` can be used: +context if `T` has an `impl const Trait for T { ... }`. + +This `new` constructor example is simplified from the following real use cases: + +1. Drop impls need to have the same generic bounds that the type declaration has. + If you want to have a const Drop implementation, + all bounds must be const Trait on the type and the Drop impl, + even if the Drop impl does not use said trait bounds. + +2. The standard library is full of cases where you have bounds on a generic type's declaration + (e.g. because the Drop impl needs them or to have earlier, more helpful, errors). + Any method (or its impl block) on that generic type will need to repeat those bounds. + Repeating those bounds will restrict the impls further than actually required. + We don't need `const Trait` bounds if all we do is store values of the generic + argument type in fields of the result type. + Examples from the standard library include, but are not limited to + * [RawVec::new](https://github.com/rust-lang/rust/blob/a7cef0bf0810d04da3101fe079a0625d2756744a/src/liballoc/raw_vec.rs#L51) + * [Iterator::map](https://github.com/rust-lang/rust/blob/a7cef0bf0810d04da3101fe079a0625d2756744a/src/libcore/iter/traits/iterator.rs#L558) + * and essentially all other `Iterator` methods that take a closure arg + * [HashMap::new](https://github.com/rust-lang/rust/blob/a7cef0bf0810d04da3101fe079a0625d2756744a/src/libstd/collections/hash/map.rs#L697) + * and `HashSet` (which is implemented on top of `HashMap`) + * [[T]::split](https://github.com/rust-lang/rust/blob/a7cef0bf0810d04da3101fe079a0625d2756744a/src/libcore/slice/mod.rs#L1045) + * and all other slice methods that take a closure + +### `?const` syntax + +Thus an opt-out similar to `?Sized` is proposed by this RFC: ```rust struct Foo(T); From 7e349a1540075c7db60b0432c7ee70902df7c088 Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Tue, 16 Apr 2019 14:53:14 +0200 Subject: [PATCH 32/36] Specifically mention that `?const` is experimental --- text/0000-const-generic-const-fn-bounds.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/text/0000-const-generic-const-fn-bounds.md b/text/0000-const-generic-const-fn-bounds.md index 6776953f40c..1c8c22b8256 100644 --- a/text/0000-const-generic-const-fn-bounds.md +++ b/text/0000-const-generic-const-fn-bounds.md @@ -332,6 +332,10 @@ struct Foo(T); ``` +This allows functions to have `T: ?const Trait` bounds on generic parameters without requiring users +to supply a `const Trait` impl for types used for `T`. This feature is added under a separate +feature gate and will be stabilized separately from (and after) `T: Trait` bounds on `const fn`. + ## `const` default method bodies Trait methods can have default bodies for methods that are used if the method is not mentioned @@ -407,6 +411,9 @@ A summary of the result of the discussion can be found at the bottom of [this bl impl. One needs to provide a `const` default impl to not make the change a breaking change. * It becomes a breaking change to add a field (even a private one) that has a `Drop` impl which is not `const Drop` (or which has such a field). +* `?const` gives a lot of control to users and may make people feel an obligation to properly + annotate all of their generic parameters so that they propagate constness as permissively as + possible, but that this will create too much burden on the community in a variety of ways. # Rationale and alternatives [rationale-and-alternatives]: #rationale-and-alternatives From fcf00a2197908a96391f8a580379214ee22b1b51 Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Sun, 1 Sep 2019 11:02:49 +0200 Subject: [PATCH 33/36] Update 0000-const-generic-const-fn-bounds.md --- text/0000-const-generic-const-fn-bounds.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/text/0000-const-generic-const-fn-bounds.md b/text/0000-const-generic-const-fn-bounds.md index 1c8c22b8256..dd76679f9ed 100644 --- a/text/0000-const-generic-const-fn-bounds.md +++ b/text/0000-const-generic-const-fn-bounds.md @@ -627,6 +627,8 @@ questions make themselves known: ``` is already legal in Rust today, even though the `F` doesn't need to be a `const` function. + Since we can't reuse this syntax, do we need a different syntax or should we just keep constants + as they are and just reuse the syntax in `const fn` arguments? 2. Opt out bounds might seem unintuitive? From 2600c3af117fe5d83898c1611a41786e4e3fb1fa Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Wed, 6 May 2020 10:00:43 +0200 Subject: [PATCH 34/36] Update text/0000-const-generic-const-fn-bounds.md Co-authored-by: Christopher Durham --- text/0000-const-generic-const-fn-bounds.md | 1 + 1 file changed, 1 insertion(+) diff --git a/text/0000-const-generic-const-fn-bounds.md b/text/0000-const-generic-const-fn-bounds.md index dd76679f9ed..d5f466da195 100644 --- a/text/0000-const-generic-const-fn-bounds.md +++ b/text/0000-const-generic-const-fn-bounds.md @@ -278,6 +278,7 @@ const fn new(t: T) -> Foo { ```
Click here for effect system syntax description + ```rust struct Foo(T); const(c) fn new(t: T) -> Foo { From fe03f3acac06bb8b84c49a28c656b7f82717d769 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Thu, 25 Jun 2020 11:17:46 +0200 Subject: [PATCH 35/36] some editing to better reflect my concern --- text/0000-const-generic-const-fn-bounds.md | 82 ++++++++++++++++------ 1 file changed, 59 insertions(+), 23 deletions(-) diff --git a/text/0000-const-generic-const-fn-bounds.md b/text/0000-const-generic-const-fn-bounds.md index d5f466da195..5c10db90455 100644 --- a/text/0000-const-generic-const-fn-bounds.md +++ b/text/0000-const-generic-const-fn-bounds.md @@ -467,6 +467,12 @@ This is strictly the least restrictive and generic variant, but is a semver hazard as changing a const fn's body to suddenly call a method that it did not before can break users of the function. +# Unresolved questions + +* Is it possible to ensure that we are consistent about opt-in vs opt-out of + constness in static trait bounds, function pointers, and `dyn Trait` while + remaining backwards compatible? Also see [this discussion][dynamic_dispatch]. + # Future work This design is explicitly forward compatible to all future extensions the author could think @@ -610,7 +616,15 @@ trait Foo { which does not force `impl const Foo for Type` to now require passing a `T` with an `impl const Bar` to the `a` method. -## `const` function pointers +## `const` function pointers and `dyn Trait` +[dynamic_dispatch]: #const-function-pointers-and-dyn-trait + +The RFC discusses const bounds on *static* dispatch. What about *dynamic* dispatch? +In Rust, that means function pointers and `dyn Trait`. + +### Function pointers + +This is illegal before and with this RFC: ```rust const fn foo(f: fn() -> i32) -> i32 { @@ -618,8 +632,13 @@ const fn foo(f: fn() -> i32) -> i32 { } ``` -is illegal before and with this RFC. While we can change the language to allow this feature, two -questions make themselves known: +To remain consistent with trait bounds as described in this RFC, it seems +reasonable to assume that a `fn` pointer passed to a `const fn` would implicitly +be required to point itself to a `const fn`, and to have an opt-out with +`?const` for cases where `foo` does not actually want to call `f` (such as +`RawWakerVTable::new`). + +However, there are two problems with this: 1. fn pointers in constants @@ -627,9 +646,10 @@ questions make themselves known: const F: fn() -> i32 = ...; ``` - is already legal in Rust today, even though the `F` doesn't need to be a `const` function. - Since we can't reuse this syntax, do we need a different syntax or should we just keep constants - as they are and just reuse the syntax in `const fn` arguments? + is already legal in Rust today, even though the `F` doesn't need to be a + `const` function. Since we can't reuse this syntax, do we need a different + syntax or should the same syntax mean different things for `const` types and + `const fn` types? 2. Opt out bounds might seem unintuitive? @@ -661,26 +681,14 @@ fn foo(f: const fn() -> i32) -> i32 { } ``` -Which is useless except for ensuring some sense of "purity" of the function pointer ensuring that +Which could be useful for ensuring some sense of "purity" of the function pointer ensuring that subsequent calls will only modify global state if passed in via arguments. -## explicit `const` bounds -[explicit_const]: #explicit-const - -`const` on the bounds (e.g. `T: const Trait`) requires an `impl const Trait` for any types used to -replace `T`. This allows `const` trait bounds on any (even non-const) functions, e.g. in - -```rust -fn foo() -> i32 { - const FOO: i32 = T::bar(); - FOO -} -``` - -Which, once `const` items and array lengths inside of functions can make use of the generics of -the function, would allow the above function to actually exist. +However, this would be inconsistent with what the RFC proposes for traits. That +is particularly surprising when we consider that the exact same concern applies +to `dyn Trait`. -## `dyn Trait` +### `dyn Trait` A natural extension to this RFC is to allow @@ -698,6 +706,34 @@ const fn foo(bar: &dyn ?const Trait) -> SomeType { } ``` +However, the same concerns as for function pointers apply. In particular, this +is already allowed on stable, without any check that the `Trait` implementation +is `const`: + +```rust +const F: &dyn Trait = ...; +``` + +Like with function pointers, we could instead make `dyn Trait` opt-in to +constness with `dyn const Trait`, but that would be inconsistent with static +trait bounds. + +## explicit `const` bounds +[explicit_const]: #explicit-const-bounds + +`const` on the bounds (e.g. `T: const Trait`) requires an `impl const Trait` for any types used to +replace `T`. This allows `const` trait bounds on any (even non-const) functions, e.g. in + +```rust +fn foo() -> i32 { + const FOO: i32 = T::bar(); + FOO +} +``` + +Which, once `const` items and array lengths inside of functions can make use of the generics of +the function, would allow the above function to actually exist. + # Unresolved questions [unresolved-questions]: #unresolved-questions From 7ac09e88a234812066029ed1ae9372c06a7c6fca Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Thu, 25 Jun 2020 18:24:52 +0200 Subject: [PATCH 36/36] more edit and reorder fn/dyn --- text/0000-const-generic-const-fn-bounds.md | 92 +++++++++------------- 1 file changed, 38 insertions(+), 54 deletions(-) diff --git a/text/0000-const-generic-const-fn-bounds.md b/text/0000-const-generic-const-fn-bounds.md index 5c10db90455..1c72aa16fc9 100644 --- a/text/0000-const-generic-const-fn-bounds.md +++ b/text/0000-const-generic-const-fn-bounds.md @@ -622,6 +622,36 @@ to the `a` method. The RFC discusses const bounds on *static* dispatch. What about *dynamic* dispatch? In Rust, that means function pointers and `dyn Trait`. +### `dyn Trait` + +Treating `dyn Trait` similar to how this RFC treats static trait bounds, we could allow + +```rust +const fn foo(bar: &dyn Trait) -> SomeType { + bar.some_method() +} +``` + +with an opt out via `?const` + +```rust +const fn foo(bar: &dyn ?const Trait) -> SomeType { + bar.some_method() // ERROR +} +``` + +However, there is a problem with this. The following code is already allowed on +stable, without any check that the `Trait` implementation is `const`: + +```rust +const F: &dyn Trait = ...; +``` + +We could instead make `dyn Trait` opt-in to constness with `dyn const Trait`, +but that would be inconsistent with how this RFC defines `const` to work around +static trait bounds. Or we could treat `dyn Trait` differently in `const` types +and `const fn` argument/return types. + ### Function pointers This is illegal before and with this RFC: @@ -638,29 +668,15 @@ be required to point itself to a `const fn`, and to have an opt-out with `?const` for cases where `foo` does not actually want to call `f` (such as `RawWakerVTable::new`). -However, there are two problems with this: - -1. fn pointers in constants +However, we have the same problem as with `dyn Trait`. The following is already +legal in Rust today, even though the `F` doesn't need to be a `const` function: - ```rust - const F: fn() -> i32 = ...; - ``` - - is already legal in Rust today, even though the `F` doesn't need to be a - `const` function. Since we can't reuse this syntax, do we need a different - syntax or should the same syntax mean different things for `const` types and - `const fn` types? - -2. Opt out bounds might seem unintuitive? +```rust +const F: fn() -> i32 = ...; +``` - ```rust - const fn foo(f: ?const fn() -> i32) -> i32 { - // not allowed to call `f` here, because we can't guarantee that it points to a `const fn` - } - const fn foo(f: fn() -> i32) -> i32 { - f() - } - ``` +Since we can't reuse this syntax, do we need a different syntax or should the +same syntax mean different things for `const` types and `const fn` types? Alternatively one can prefix function pointers to `const` functions with `const`: @@ -684,39 +700,7 @@ fn foo(f: const fn() -> i32) -> i32 { Which could be useful for ensuring some sense of "purity" of the function pointer ensuring that subsequent calls will only modify global state if passed in via arguments. -However, this would be inconsistent with what the RFC proposes for traits. That -is particularly surprising when we consider that the exact same concern applies -to `dyn Trait`. - -### `dyn Trait` - -A natural extension to this RFC is to allow - -```rust -const fn foo(bar: &dyn Trait) -> SomeType { - bar.some_method() -} -``` - -with an opt out via `?const` - -```rust -const fn foo(bar: &dyn ?const Trait) -> SomeType { - bar.some_method() // ERROR -} -``` - -However, the same concerns as for function pointers apply. In particular, this -is already allowed on stable, without any check that the `Trait` implementation -is `const`: - -```rust -const F: &dyn Trait = ...; -``` - -Like with function pointers, we could instead make `dyn Trait` opt-in to -constness with `dyn const Trait`, but that would be inconsistent with static -trait bounds. +However, as with `dyn Trait` above, this would be inconsistent with what the RFC proposes for traits. ## explicit `const` bounds [explicit_const]: #explicit-const-bounds