From d424f1d82e13a04f11ea14e0e01b1ef898d7093a Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Fri, 5 Oct 2018 14:44:01 +0200 Subject: [PATCH 01/26] const generics RFC --- const-generic-const-fn-bounds.md | 123 +++++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 const-generic-const-fn-bounds.md diff --git a/const-generic-const-fn-bounds.md b/const-generic-const-fn-bounds.md new file mode 100644 index 0000000..a0c9665 --- /dev/null +++ b/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 58fbcd7c9b837f3dfabebf0e671635da07d942aa Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Sat, 13 Oct 2018 10:11:52 +0200 Subject: [PATCH 02/26] Address review comments --- const-generic-const-fn-bounds.md | 82 ++++++++++++++++++++++---------- 1 file changed, 57 insertions(+), 25 deletions(-) diff --git a/const-generic-const-fn-bounds.md b/const-generic-const-fn-bounds.md index a0c9665..87094be 100644 --- a/const-generic-const-fn-bounds.md +++ b/const-generic-const-fn-bounds.md @@ -8,8 +8,9 @@ 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. +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,26 +22,26 @@ 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 call call methods of generic parameters of a const function, because they are implicitly assumed to be +`const fn`. For example, the `Add` trait declaration has an additional `const` before the trait name, so you can use it as a trait bound on your generic parameters: ```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 } ``` 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 { fn add(self, other: Self) -> Self { - MyInt(self.0, other.0) + MyInt(self.0 + other.0) } } ``` @@ -65,23 +66,17 @@ const impl Hash for MyInt { # 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. +The implementation of this RFC is (in contrast to some of its alternatives) mostly +changes around the syntax of the language (adding `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. # 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 @@ -97,13 +92,17 @@ 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 +## No 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. +One could require no `const` on the bounds (e.g. `T: Trait`) and assume constness for all +bounds. An opt-out via `T: ?const Trait` would then allow declaring bounds that cannot be +used for calling methods. This design causes discrepancies with `const fn` pointers as +arguments (where the constness would be needed, as normal function pointers already exist +as the type of constants). Also it is not forward compatible to allowing `const` trait bounds +on non-const functions ## Infer all the things @@ -117,7 +116,40 @@ This is strictly the most powerful and generic variant, but is an enormous backw 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 + +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). + # Unresolved questions [unresolved-questions]: #unresolved-questions -None \ No newline at end of file +Should `const impl` blocks additionally generate impls that are not const if any generic +parameters are not const? + +E.g. + +```rust +const impl Add for Foo { + fn add(self, other: Self) -> Self { + Foo(self.0 + other.0) + } +} +``` + +would allow calling `Foo(String::new()) + Foo(String::new())` even though that is (at the time +of writing this RFC) most definitely not const. + +This would go 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. \ No newline at end of file From 1bd40724e9be815bdaf35110fd0535a7cc9977ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20S=CC=B6c=CC=B6h=CC=B6n=CC=B6e=CC=B6i=CC=B6d=CC=B6?= =?UTF-8?q?e=CC=B6r=20Scherer?= Date: Fri, 26 Oct 2018 00:12:58 +0200 Subject: [PATCH 03/26] Add question about `Drop::drop` calls --- const-generic-const-fn-bounds.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/const-generic-const-fn-bounds.md b/const-generic-const-fn-bounds.md index 87094be..7f12f67 100644 --- a/const-generic-const-fn-bounds.md +++ b/const-generic-const-fn-bounds.md @@ -152,4 +152,9 @@ of writing this RFC) most definitely not const. This would go 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. \ No newline at end of file +a const context. + +## Drop + +Should we also allow `(SomeDropType, 42).1` as an expression if `SomeDropType`'s `Drop` impl +was declared with `const impl Drop`? From 239e6a127854e35f6e0f858353f6cd5d2b882758 Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Mon, 5 Nov 2018 14:16:26 +0100 Subject: [PATCH 04/26] Address review comments --- const-generic-const-fn-bounds.md | 63 +++++++++++++++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) diff --git a/const-generic-const-fn-bounds.md b/const-generic-const-fn-bounds.md index 7f12f67..8cd0c64 100644 --- a/const-generic-const-fn-bounds.md +++ b/const-generic-const-fn-bounds.md @@ -131,6 +131,46 @@ It might also be desirable to make the automatic `Fn*` impls on function types a 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 `const impl`s + +```rust +#[derive(Clone)] +pub struct Foo(Bar); + +struct Bar; + +const impl 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 `const impl 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. + +## 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 `const impl 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. + # Unresolved questions [unresolved-questions]: #unresolved-questions @@ -148,7 +188,8 @@ const impl Add for Foo { ``` would allow calling `Foo(String::new()) + Foo(String::new())` even though that is (at the time -of writing this RFC) most definitely not const. +of writing this RFC) most definitely not const, because `String` only has an `impl Add for String` +and not a `const impl Add for String`. This would go 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 @@ -158,3 +199,23 @@ a const context. Should we also allow `(SomeDropType, 42).1` as an expression if `SomeDropType`'s `Drop` impl was declared with `const impl Drop`? + +## Require `const` bounds on everything inside a `const impl` block? + +Instead of inferring `const`ness on all bounds and functions inside a `const impl` block, +we force the user to supply these bounds. This is more consistent with not inferring `const` +on `const` function argument types and generic bounds. The `Hash` example from above would +then look like + +```rust +const impl Hash for MyInt { + const fn hash( + &self, + state: &mut H, + ) + where H: const Hasher + { + state.write(&[self.0 as u8]); + } +} +``` From a27e2081efcd4e6c87a5fb192638daa082747fdc Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Mon, 5 Nov 2018 17:16:34 +0100 Subject: [PATCH 05/26] Offer a solution for deriving const impls --- const-generic-const-fn-bounds.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/const-generic-const-fn-bounds.md b/const-generic-const-fn-bounds.md index 8cd0c64..61ce329 100644 --- a/const-generic-const-fn-bounds.md +++ b/const-generic-const-fn-bounds.md @@ -150,6 +150,24 @@ impl would suddenly stop being `const`, without any visible change to the API. T 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; + +const impl Clone for Bar { + fn clone(&self) -> Self { Bar } +} +``` + +which would generate a `const impl 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 `const impl`. 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 From caea9b4cc250c7bd4c6d14eeb0d2b751d60831e6 Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Fri, 14 Dec 2018 12:13:05 +0100 Subject: [PATCH 06/26] =?UTF-8?q?const=20impl=20Drop=20for=20=F0=9F=8E=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- const-generic-const-fn-bounds.md | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/const-generic-const-fn-bounds.md b/const-generic-const-fn-bounds.md index 61ce329..ab26049 100644 --- a/const-generic-const-fn-bounds.md +++ b/const-generic-const-fn-bounds.md @@ -63,6 +63,23 @@ const impl Hash for MyInt { } ``` +## Drop + +A notable use case of `const impl` is defining `Drop` impls. If you write + +```rust +struct SomeDropType<'a>(&'a Cell); +const impl Drop for SomeDropType { + fn drop(&mut self) { + self.0.set(self.0.get() - 1); + } +} +``` + +Then you are allowed to actually let a value of `SomeDropType` get dropped within a constant +evaluation. This means `(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. + # Reference-level explanation [reference-level-explanation]: #reference-level-explanation @@ -213,11 +230,6 @@ This would go in hand with the current scheme for const functions, which may als at runtime with runtime arguments, but are checked for soundness as if they were called in a const context. -## Drop - -Should we also allow `(SomeDropType, 42).1` as an expression if `SomeDropType`'s `Drop` impl -was declared with `const impl Drop`? - ## Require `const` bounds on everything inside a `const impl` block? Instead of inferring `const`ness on all bounds and functions inside a `const impl` block, From ae05ba25c475e77cbd8c1215fc3b397e949a0894 Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Fri, 14 Dec 2018 12:13:19 +0100 Subject: [PATCH 07/26] Expand on implementation details a little --- const-generic-const-fn-bounds.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/const-generic-const-fn-bounds.md b/const-generic-const-fn-bounds.md index ab26049..f17fbbc 100644 --- a/const-generic-const-fn-bounds.md +++ b/const-generic-const-fn-bounds.md @@ -87,7 +87,9 @@ The implementation of this RFC is (in contrast to some of its alternatives) most changes around the syntax of the language (adding `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. +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 `const impl` items. # Drawbacks [drawbacks]: #drawbacks From 55f6739d40a07884b950c9fa13bd997367774233 Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Sat, 15 Dec 2018 12:07:33 +0100 Subject: [PATCH 08/26] `const impl` -> `impl const` --- const-generic-const-fn-bounds.md | 38 ++++++++++++++++---------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/const-generic-const-fn-bounds.md b/const-generic-const-fn-bounds.md index f17fbbc..ffda472 100644 --- a/const-generic-const-fn-bounds.md +++ b/const-generic-const-fn-bounds.md @@ -6,7 +6,7 @@ # 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 functions and allow the body of the const fn to call methods on the generic parameters that have a `const` modifier @@ -39,7 +39,7 @@ The obligation is passed to the caller of your `triple_add` function to supply a ```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) } @@ -51,7 +51,7 @@ 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, @@ -65,11 +65,11 @@ const impl Hash for MyInt { ## Drop -A notable use case of `const impl` is defining `Drop` impls. If you write +A notable use case of `impl const` is defining `Drop` impls. If you write ```rust struct SomeDropType<'a>(&'a Cell); -const impl Drop for SomeDropType { +impl const Drop for SomeDropType { fn drop(&mut self) { self.0.set(self.0.get() - 1); } @@ -89,7 +89,7 @@ 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 `const impl` items. +of `impl const` items. # Drawbacks [drawbacks]: #drawbacks @@ -150,7 +150,7 @@ It might also be desirable to make the automatic `Fn*` impls on function types a 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 `const impl`s +## Deriving `impl const` ```rust #[derive(Clone)] @@ -158,13 +158,13 @@ pub struct Foo(Bar); struct Bar; -const impl Clone for 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 `const impl Clone for Bar` (a private type) is changed to just `impl`, `Foo`'s `Clone` +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. @@ -177,13 +177,13 @@ pub struct Foo(Bar); struct Bar; -const impl Clone for Bar { +impl const Clone for Bar { fn clone(&self) -> Self { Bar } } ``` -which would generate a `const impl 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 `const impl`. The obligation is +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. @@ -200,7 +200,7 @@ const context. It seems like a natural extension to this RFC to allow const fn foo() -> impl const Bar { /* code here */ } ``` -which requires that the function only returns types with `const impl Bar` blocks. +which requires that the function only returns types with `impl const Bar` blocks. ## Specialization @@ -211,13 +211,13 @@ and `const` modifiers on `impl` blocks. # Unresolved questions [unresolved-questions]: #unresolved-questions -Should `const impl` blocks additionally generate impls that are not const if any generic +Should `impl const` blocks additionally generate impls that are not const if any generic parameters are not const? E.g. ```rust -const impl Add for Foo { +impl const Add for Foo { fn add(self, other: Self) -> Self { Foo(self.0 + other.0) } @@ -226,21 +226,21 @@ const impl Add for Foo { would allow calling `Foo(String::new()) + Foo(String::new())` even though that is (at the time of writing this RFC) most definitely not const, because `String` only has an `impl Add for String` -and not a `const impl Add for String`. +and not an `impl const Add for String`. This would go 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. -## Require `const` bounds on everything inside a `const impl` block? +## Require `const` bounds on everything inside an `impl const` block? -Instead of inferring `const`ness on all bounds and functions inside a `const impl` block, +Instead of inferring `const`ness on all bounds and functions inside a `impl const` block, we force the user to supply these bounds. This is more consistent with not inferring `const` on `const` function argument types and generic bounds. The `Hash` example from above would then look like ```rust -const impl Hash for MyInt { +impl const Hash for MyInt { const fn hash( &self, state: &mut H, From 676988bd47645185679bdb4123b33c7bf7d59c74 Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Tue, 18 Dec 2018 12:44:33 +0100 Subject: [PATCH 09/26] Talk more about runtime calls --- const-generic-const-fn-bounds.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/const-generic-const-fn-bounds.md b/const-generic-const-fn-bounds.md index ffda472..8527353 100644 --- a/const-generic-const-fn-bounds.md +++ b/const-generic-const-fn-bounds.md @@ -211,6 +211,8 @@ and `const` modifiers on `impl` blocks. # Unresolved questions [unresolved-questions]: #unresolved-questions +## Runtime uses don't have `const` restrictions? + Should `impl const` blocks additionally generate impls that are not const if any generic parameters are not const? @@ -224,7 +226,7 @@ impl const Add for Foo { } ``` -would allow calling `Foo(String::new()) + Foo(String::new())` even though that is (at the time +would allow calling `Foo(String::from("foo")) + Foo(String::from("bar"))` even though that is (at the time of writing this RFC) most definitely not const, because `String` only has an `impl Add for String` and not an `impl const Add for String`. From 5890176181f9e59c2a6fe688d1f1f4e14d24e868 Mon Sep 17 00:00:00 2001 From: varkor Date: Wed, 9 Jan 2019 16:00:47 +0100 Subject: [PATCH 10/26] Double call Co-Authored-By: oli-obk --- const-generic-const-fn-bounds.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/const-generic-const-fn-bounds.md b/const-generic-const-fn-bounds.md index 8527353..0f5161e 100644 --- a/const-generic-const-fn-bounds.md +++ b/const-generic-const-fn-bounds.md @@ -22,7 +22,7 @@ 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 a const function, because they are implicitly assumed to be +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 declaration has an additional `const` before the trait name, so you can use it as a trait bound on your generic parameters: From 834a259c87dead77e91d4aa3a02c80d3a847469e Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Wed, 9 Jan 2019 17:04:02 +0100 Subject: [PATCH 11/26] Resolve unresolved issues and add implementation instructions --- const-generic-const-fn-bounds.md | 104 +++++++++++++++++-------------- 1 file changed, 58 insertions(+), 46 deletions(-) diff --git a/const-generic-const-fn-bounds.md b/const-generic-const-fn-bounds.md index 0f5161e..cc26e84 100644 --- a/const-generic-const-fn-bounds.md +++ b/const-generic-const-fn-bounds.md @@ -46,23 +46,26 @@ impl const Add for MyInt { } ``` -The const requirement is propagated to all bounds of the impl or its methods, +The const requirement is required 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 impl const Hash for MyInt { - fn hash( + const fn hash( &self, state: &mut H, ) - where H: Hasher + where H: const Hasher { state.write(&[self.0 as u8]); } } ``` +While these `const` keywords could be inferred (after all, they are required), requiring them is +forward compatible to schemes in the future that allow more fine grained control. + ## Drop A notable use case of `impl const` is defining `Drop` impls. If you write @@ -80,17 +83,67 @@ Then you are allowed to actually let a value of `SomeDropType` get dropped withi evaluation. This means `(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. +## Runtime uses don't have `const` restrictions? + +`impl const` blocks additionally generate impls that are not const if any generic +parameters are not const. + +E.g. + +```rust +impl const Add for Foo { + fn add(self, other: Self) -> Self { + Foo(self.0 + other.0) + } +} +``` + +allows calling `Foo(String::from("foo")) + Foo(String::from("bar"))` even though that is (at the time +of writing this RFC) most definitely not const, because `String` only has an `impl Add for String` +and not an `impl const Add for String`. + +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(String::from("foo"), String::from("bar"))` at runtime. + +```rust +const fn add(a: T, b: T) -> T { + a + b +} +``` + +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. + # Reference-level explanation [reference-level-explanation]: #reference-level-explanation The implementation of this RFC is (in contrast to some of its alternatives) mostly -changes around the syntax of the language (adding `const` modifiers in a few places) +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. +## Implementation instructions + +1. Add an `is_const` field to the AST's `TraitRef` +2. Adjust the Parser to support `const` modifiers before trait bounds +3. Add an `is_const` field to the HIR's `TraitRef` +4. Adjust lowering to pass through the `is_const` field from AST to HIR +5. Add a a check to `librustc_typeck/check/wfcheck.rs` ensuring that all generic bounds + in an `impl const` block have the `in_const` flag set and all methods' `constness` field is + `Const`. +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. + # Drawbacks [drawbacks]: #drawbacks @@ -211,45 +264,4 @@ and `const` modifiers on `impl` blocks. # Unresolved questions [unresolved-questions]: #unresolved-questions -## Runtime uses don't have `const` restrictions? - -Should `impl const` blocks additionally generate impls that are not const if any generic -parameters are not const? - -E.g. - -```rust -impl const Add for Foo { - fn add(self, other: Self) -> Self { - Foo(self.0 + other.0) - } -} -``` - -would allow calling `Foo(String::from("foo")) + Foo(String::from("bar"))` even though that is (at the time -of writing this RFC) most definitely not const, because `String` only has an `impl Add for String` -and not an `impl const Add for String`. - -This would go 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. - -## Require `const` bounds on everything inside an `impl const` block? - -Instead of inferring `const`ness on all bounds and functions inside a `impl const` block, -we force the user to supply these bounds. This is more consistent with not inferring `const` -on `const` function argument types and generic bounds. The `Hash` example from above would -then look like - -```rust -impl const Hash for MyInt { - const fn hash( - &self, - state: &mut H, - ) - where H: const Hasher - { - state.write(&[self.0 as u8]); - } -} -``` +Everything has been addressed in the reviews From 4dea80ca028f5f562db40a737ca2828bb98bb1da Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Wed, 9 Jan 2019 17:14:07 +0100 Subject: [PATCH 12/26] Coevolution is cool --- const-generic-const-fn-bounds.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/const-generic-const-fn-bounds.md b/const-generic-const-fn-bounds.md index cc26e84..52f3eed 100644 --- a/const-generic-const-fn-bounds.md +++ b/const-generic-const-fn-bounds.md @@ -144,6 +144,18 @@ of `impl const` items. 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. + # Drawbacks [drawbacks]: #drawbacks From 237cdadde2c52bf3f15f2af8f8aabf656715bb83 Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Mon, 14 Jan 2019 10:17:33 +0100 Subject: [PATCH 13/26] Elaborate on `const Drop` fields --- const-generic-const-fn-bounds.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/const-generic-const-fn-bounds.md b/const-generic-const-fn-bounds.md index 52f3eed..3207b4d 100644 --- a/const-generic-const-fn-bounds.md +++ b/const-generic-const-fn-bounds.md @@ -83,6 +83,16 @@ Then you are allowed to actually let a value of `SomeDropType` get dropped withi evaluation. This means `(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 additionally generate impls that are not const if any generic From 5948264b2354e4f2dd58de5c1692ac6078879ae4 Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Mon, 14 Jan 2019 13:36:32 +0100 Subject: [PATCH 14/26] Inferred and opt-out const bounds --- const-generic-const-fn-bounds.md | 159 ++++++++++++++++++++++++++----- 1 file changed, 134 insertions(+), 25 deletions(-) diff --git a/const-generic-const-fn-bounds.md b/const-generic-const-fn-bounds.md index 3207b4d..ebda125 100644 --- a/const-generic-const-fn-bounds.md +++ b/const-generic-const-fn-bounds.md @@ -23,11 +23,11 @@ generic parameter type), because they are fully unconstrained. [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 declaration has an additional `const` before the trait name, so -you can use it as a trait bound on your generic parameters: +`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 { +const fn triple_add(a: T, b: T, c: T) -> T { a + b + c } ``` @@ -46,26 +46,23 @@ impl const Add for MyInt { } ``` -The const requirement is required on all bounds of the impl and its methods, +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 impl const Hash for MyInt { - const fn hash( + fn hash( &self, state: &mut H, ) - where H: const Hasher + where H: Hasher { state.write(&[self.0 as u8]); } } ``` -While these `const` keywords could be inferred (after all, they are required), requiring them is -forward compatible to schemes in the future that allow more fine grained control. - ## Drop A notable use case of `impl const` is defining `Drop` impls. If you write @@ -101,7 +98,7 @@ parameters are not const. E.g. ```rust -impl const Add for Foo { +impl const Add for Foo { fn add(self, other: Self) -> Self { Foo(self.0 + other.0) } @@ -118,7 +115,7 @@ a const context. E.g. the following function may be called as `add(String::from("foo"), String::from("bar"))` at runtime. ```rust -const fn add(a: T, b: T) -> T { +const fn add(a: T, b: T) -> T { a + b } ``` @@ -129,6 +126,28 @@ 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` methods: + +```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` method 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) +} +``` + # Reference-level explanation [reference-level-explanation]: #reference-level-explanation @@ -142,13 +161,12 @@ of `impl const` items. ## Implementation instructions -1. Add an `is_const` field to the AST's `TraitRef` -2. Adjust the Parser to support `const` modifiers before trait bounds -3. Add an `is_const` field to the HIR's `TraitRef` -4. Adjust lowering to pass through the `is_const` field from AST to HIR -5. Add a a check to `librustc_typeck/check/wfcheck.rs` ensuring that all generic bounds - in an `impl const` block have the `in_const` flag set and all methods' `constness` field is - `Const`. +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 @@ -189,14 +207,18 @@ can be applied to specific methods. E.g. `where ::add: const` or somet the sort. This design is more complex than the current one and we'd probably want the current one as sugar anyway -## No explicit `const` bounds +## Require `const` bounds everywhere -One could require no `const` on the bounds (e.g. `T: Trait`) and assume constness for all -bounds. An opt-out via `T: ?const Trait` would then allow declaring bounds that cannot be -used for calling methods. This design causes discrepancies with `const fn` pointers as -arguments (where the constness would be needed, as normal function pointers already exist -as the type of constants). Also it is not forward compatible to allowing `const` trait bounds -on non-const functions +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 @@ -283,6 +305,93 @@ Impl specialization is still unstable. There should be a separate RFC for declar 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. + +## `?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 are ugly + + I don't think it's either intuitive nor readable to write the following + + ```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` + } + ``` + +Thus it seems useful to 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. + # Unresolved questions [unresolved-questions]: #unresolved-questions From 7d86f874cdacaa1ff7c9d40e8ffdb0dbc11f7a39 Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Mon, 14 Jan 2019 20:13:29 +0100 Subject: [PATCH 15/26] Clarify where the `new` function cannot be called --- const-generic-const-fn-bounds.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/const-generic-const-fn-bounds.md b/const-generic-const-fn-bounds.md index ebda125..91d5838 100644 --- a/const-generic-const-fn-bounds.md +++ b/const-generic-const-fn-bounds.md @@ -129,7 +129,7 @@ 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` methods: +call any of the methods on those generic bounds. Prominent examples are `new` functions: ```rust struct Foo(T); @@ -138,7 +138,8 @@ const fn new(t: T) -> Foo { } ``` -Unfortunately, with the given syntax in this RFC, one can now only call the `new` method if `T` has +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 From 3f3875795f234b89116bff10a33ba678e8e0bdba Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Tue, 15 Jan 2019 09:12:33 +0100 Subject: [PATCH 16/26] Add `const trait` sugar future extension --- const-generic-const-fn-bounds.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/const-generic-const-fn-bounds.md b/const-generic-const-fn-bounds.md index 91d5838..ec021e1 100644 --- a/const-generic-const-fn-bounds.md +++ b/const-generic-const-fn-bounds.md @@ -328,6 +328,22 @@ const fn foo() -> i32 { even though the `?const` modifier explicitly opts out of constness. +### `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 From 2c6414ed34569df2b345e04a073c6ea1baafc837 Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Tue, 15 Jan 2019 09:29:34 +0100 Subject: [PATCH 17/26] Link to new blog post about the theoretical discussion --- const-generic-const-fn-bounds.md | 1 + 1 file changed, 1 insertion(+) diff --git a/const-generic-const-fn-bounds.md b/const-generic-const-fn-bounds.md index ec021e1..ad42cac 100644 --- a/const-generic-const-fn-bounds.md +++ b/const-generic-const-fn-bounds.md @@ -184,6 +184,7 @@ functions. The discussion and evolution of the type theoretical scheme can be fo [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 From f33fe7a81b9944d15403d72cb4ee7acb859078ba Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Tue, 15 Jan 2019 09:37:44 +0100 Subject: [PATCH 18/26] Explain const bounds on impl blocks and associated types --- const-generic-const-fn-bounds.md | 80 ++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/const-generic-const-fn-bounds.md b/const-generic-const-fn-bounds.md index ad42cac..6794ad7 100644 --- a/const-generic-const-fn-bounds.md +++ b/const-generic-const-fn-bounds.md @@ -63,6 +63,33 @@ impl const 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 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 `impl` blocks + +Similar to generic parameters on `const` functions, one can have generic parameters on `impl` blocks. +These follow the same rules as bounds on `const` functions: + +* all bounds are required to have `impl const` for substituted types if the impl is used in a const context + * except in the presence of `?const` (see below) +* if the impl is used at runtime, there are no restrictions what kind of bounds are required + ## Drop A notable use case of `impl const` is defining `Drop` impls. If you write @@ -329,6 +356,23 @@ const fn foo() -> i32 { 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`: @@ -410,6 +454,42 @@ fn foo(f: const fn() -> i32) -> i32 { 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. + +## `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 author of this RFC considers prepending the default body's +method signature with `const` to be the most intuitive syntax. + +```rust +trait Foo { + const fn bar() {} +} +``` + +While this conflicts with other future work ideas like `const` trait methods or `const trait` declarations, +these features are unnecessary for full expressiveness as discussed in their respective sections. + # Unresolved questions [unresolved-questions]: #unresolved-questions From 0293ea78704293c2e2b95647cbd2d91f131683bd Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Tue, 15 Jan 2019 12:45:29 +0100 Subject: [PATCH 19/26] We need const default bodies for non-breaking changes --- const-generic-const-fn-bounds.md | 42 ++++++++++++++++---------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/const-generic-const-fn-bounds.md b/const-generic-const-fn-bounds.md index 6794ad7..b62286f 100644 --- a/const-generic-const-fn-bounds.md +++ b/const-generic-const-fn-bounds.md @@ -176,6 +176,27 @@ const fn new(t: T) -> Foo { } ``` +## `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 author of this RFC considers prepending the default body's +method signature with `const` to be the most intuitive syntax. + +```rust +trait Foo { + 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 @@ -469,27 +490,6 @@ fn foo() -> i32 { 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. -## `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 author of this RFC considers prepending the default body's -method signature with `const` to be the most intuitive syntax. - -```rust -trait Foo { - const fn bar() {} -} -``` - -While this conflicts with other future work ideas like `const` trait methods or `const trait` declarations, -these features are unnecessary for full expressiveness as discussed in their respective sections. - # Unresolved questions [unresolved-questions]: #unresolved-questions From ded5c406b86e0d957cc94da8930a69fbcafd8e55 Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Tue, 15 Jan 2019 15:28:05 +0100 Subject: [PATCH 20/26] Placeholder syntax for method bodies that are const --- const-generic-const-fn-bounds.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/const-generic-const-fn-bounds.md b/const-generic-const-fn-bounds.md index b62286f..ff337fd 100644 --- a/const-generic-const-fn-bounds.md +++ b/const-generic-const-fn-bounds.md @@ -185,12 +185,14 @@ in an `impl`. This has several uses, most notably * 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 author of this RFC considers prepending the default body's -method signature with `const` to be the most intuitive syntax. +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 +`#[default_method_body_is_const]` attribute to the method. ```rust trait Foo { - const fn bar() {} + #[default_method_body_is_const] + fn bar() {} } ``` @@ -493,4 +495,5 @@ the function, would allow the above function to actually exist. # Unresolved questions [unresolved-questions]: #unresolved-questions -Everything has been addressed in the reviews +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. From 98e5a3c525fc7aac03297253efd8d7ee8cec8062 Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Wed, 23 Jan 2019 09:14:57 +0100 Subject: [PATCH 21/26] Missing keyword --- const-generic-const-fn-bounds.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/const-generic-const-fn-bounds.md b/const-generic-const-fn-bounds.md index ff337fd..9859e3c 100644 --- a/const-generic-const-fn-bounds.md +++ b/const-generic-const-fn-bounds.md @@ -70,7 +70,7 @@ for the associated type: trait Foo { type Bar: Add; } -impl Foo for A { +impl const Foo for A { type Bar = B; // B must have an `impl const Add for B` } ``` From d4637f9e0e0b803323488d0aab850e4d8622e4e4 Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Wed, 23 Jan 2019 09:19:26 +0100 Subject: [PATCH 22/26] The title is not a question --- const-generic-const-fn-bounds.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/const-generic-const-fn-bounds.md b/const-generic-const-fn-bounds.md index 9859e3c..31fca92 100644 --- a/const-generic-const-fn-bounds.md +++ b/const-generic-const-fn-bounds.md @@ -117,7 +117,7 @@ struct Bar(Foo); impl const Drop for Foo { fn drop(&mut self) {} } // not allowed ``` -## Runtime uses don't have `const` restrictions? +## Runtime uses don't have `const` restrictions `impl const` blocks additionally generate impls that are not const if any generic parameters are not const. From 9c715d7e51fe297c602fa8cbcb73fd5cd683c182 Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Wed, 23 Jan 2019 11:29:26 +0100 Subject: [PATCH 23/26] Explain with effect system syntax --- const-generic-const-fn-bounds.md | 34 +++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/const-generic-const-fn-bounds.md b/const-generic-const-fn-bounds.md index 31fca92..c5fc206 100644 --- a/const-generic-const-fn-bounds.md +++ b/const-generic-const-fn-bounds.md @@ -119,8 +119,8 @@ impl const Drop for Foo { fn drop(&mut self) {} } // not allowed ## Runtime uses don't have `const` restrictions -`impl const` blocks additionally generate impls that are not const if any generic -parameters are not const. +`impl const` blocks are treated as if the constness is a generic parameter +(see also effect systems in the alternatives). E.g. @@ -134,7 +134,23 @@ impl const Add for Foo { allows calling `Foo(String::from("foo")) + Foo(String::from("bar"))` even though that is (at the time of writing this RFC) most definitely not const, because `String` only has an `impl Add for String` -and not an `impl const Add for String`. +and not an `impl const Add for String`. 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. +Of course 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 @@ -147,6 +163,18 @@ const fn add(a: T, b: T) -> T { } ``` +Using the same effect syntax from above: + +```rust + const(c) fn add(a: T, b: T) -> 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. From a5e07d83ce0ec3f50c489ce5bd739095ef775572 Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Wed, 23 Jan 2019 12:16:06 +0100 Subject: [PATCH 24/26] Rusty effect systems --- const-generic-const-fn-bounds.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/const-generic-const-fn-bounds.md b/const-generic-const-fn-bounds.md index c5fc206..f710e6e 100644 --- a/const-generic-const-fn-bounds.md +++ b/const-generic-const-fn-bounds.md @@ -139,7 +139,7 @@ effect syntax nor effect semantics are proposed by this RFC, the following is ju purposes): ```rust -impl const(c) Add for Foo { +impl const(c) Add for Foo { const(c) fn add(self, other: Self) -> Self { Foo(self.0 + other.0) } @@ -166,7 +166,7 @@ const fn add(a: T, b: T) -> T { Using the same effect syntax from above: ```rust - const(c) fn add(a: T, b: T) -> T { + const(c) fn add(a: T, b: T) -> T { a + b } ``` From 9edf8eca050489fcd5386d443fa3fb2ecd587420 Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Wed, 23 Jan 2019 13:27:53 +0100 Subject: [PATCH 25/26] `impl const` blocks are not just "similar" to `const fn`, they behave exactly the same --- const-generic-const-fn-bounds.md | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/const-generic-const-fn-bounds.md b/const-generic-const-fn-bounds.md index f710e6e..43dc63b 100644 --- a/const-generic-const-fn-bounds.md +++ b/const-generic-const-fn-bounds.md @@ -81,14 +81,24 @@ 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 `impl` blocks +## Generic bounds -Similar to generic parameters on `const` functions, one can have generic parameters on `impl` blocks. -These follow the same rules as bounds on `const` functions: +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`. -* all bounds are required to have `impl const` for substituted types if the impl is used in a const context - * except in the presence of `?const` (see below) -* if the impl is used at runtime, there are no restrictions what kind of bounds are required +```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 From 56f394b312c79ef7b64da42f4a84c93db7ad6ceb Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Tue, 5 Feb 2019 10:21:27 +0100 Subject: [PATCH 26/26] Integrate centril's review --- const-generic-const-fn-bounds.md | 109 ++++++++++++++++++++++++------- 1 file changed, 87 insertions(+), 22 deletions(-) diff --git a/const-generic-const-fn-bounds.md b/const-generic-const-fn-bounds.md index 43dc63b..69e2251 100644 --- a/const-generic-const-fn-bounds.md +++ b/const-generic-const-fn-bounds.md @@ -46,6 +46,9 @@ 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. + 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. @@ -102,9 +105,16 @@ bounds for types substituted for `T`. ## Drop -A notable use case of `impl const` is defining `Drop` impls. If you write +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) { @@ -113,8 +123,14 @@ impl const Drop for SomeDropType { } ``` -Then you are allowed to actually let a value of `SomeDropType` get dropped within a constant -evaluation. This means `(SomeDropType(&Cell::new(42)), 42).1` is now allowed, because we can prove +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 @@ -140,11 +156,24 @@ impl const Add for Foo { 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(String::from("foo")) + Foo(String::from("bar"))` even though that is (at the time -of writing this RFC) most definitely not const, because `String` only has an `impl Add for String` -and not an `impl const Add for String`. Expressed in some sort of effect system syntax (neither +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): @@ -159,25 +188,25 @@ 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. -Of course for regular impls one can still pass a `T` which has a `const Add` impl, but that won't +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(String::from("foo"), String::from("bar"))` at runtime. +`add(Bar, Bar)` at runtime. ```rust -const fn add(a: T, b: T) -> T { - a + b +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: T) -> T { - a + b + const(c) fn add>(a: T, b: U) -> T { + -a + b } ``` @@ -225,7 +254,7 @@ in an `impl`. This has several uses, most notably 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 -`#[default_method_body_is_const]` attribute to the method. +placeholder `#[default_method_body_is_const]` attribute to the method. ```rust trait Foo { @@ -286,7 +315,7 @@ but it 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 @@ -295,13 +324,13 @@ One could annotate methods instead of impls, allowing just marking some method i 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. This design is more complex than the current one and we'd probably want the -current one as sugar anyway +current one as sugar anyway. ## Require `const` bounds everywhere 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 +on non-const functions, e.g. in: ```rust fn foo() -> i32 { @@ -318,7 +347,7 @@ 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. @@ -331,7 +360,8 @@ about. Notable mentions (see also the alternatives section): * 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 +* 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 @@ -483,17 +513,18 @@ themselves known: is already legal in Rust today, even though the `F` doesn't need to be a `const` function. -2. Opt out bounds are ugly - - I don't think it's either intuitive nor readable to write the following +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() + } ``` -Thus it seems useful to prefix function pointers to `const` functions with `const`: +Alternatively one can prefix function pointers to `const` functions with `const`: ```rust const fn foo(f: const fn() -> i32) -> i32 { @@ -530,8 +561,42 @@ fn foo() -> i32 { 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 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 +} +```