From 5ccb3b374556a642e6a98bd4ced886532a6654ed Mon Sep 17 00:00:00 2001 From: Mazdak Date: Tue, 5 Dec 2017 09:01:01 +0100 Subject: [PATCH 1/7] rfc, const_bounds_methods: half way there --- text/0000-const-bounds-methods.md | 199 ++++++++++++++++++++++++++++++ 1 file changed, 199 insertions(+) create mode 100644 text/0000-const-bounds-methods.md diff --git a/text/0000-const-bounds-methods.md b/text/0000-const-bounds-methods.md new file mode 100644 index 00000000000..564db2bdd3e --- /dev/null +++ b/text/0000-const-bounds-methods.md @@ -0,0 +1,199 @@ +- Feature Name: const_bounds_methods +- Start Date: 2017-12-05 +- RFC PR: (leave this empty) +- Rust Issue: (leave this empty) + +# Summary +[summary]: #summary + +Allows for: + +1. `const fn` in `trait`s. + +2. over-constraining trait `fn`s as `const fn` in `impl`s. + +3. syntactic sugar `const impl` for `impl`s where all fn:s are const. + +4. `const` bounds as in `T: const Trait` satisfied by `T`s with only +`const fn`s in their `impl Trait for T {..}`. + +# Motivation +[motivation]: #motivation + +This RFC adds a non-trivial amount of expressive power to the language. + +Let us go throw the motivation for each bit in order. + +## 1. `const fn` in traits + +Currently, associated constants are the only part of the constant time +evaluation that is available for `trait`s. + +But there are useful `const fn`s besides those which associated constants model +(those from `() -> T` - i.e: a value not depending on inputs) where the +`const fn`s depend on inputs, be they `const` or other. + +It is also inconsistent not to have `const fn`s in `trait`s as `fn` and +`unsafe fn` are both allowed today. + +## 2. over-constraining `trait` `fn`s as `const fn` in `impl`s + +This allows the user to be more strict and less allowing than the `trait` +permits. The expressive power gained here is a) that the user may statically +check that the `fn` may not do certain things, b) that when all `fn`s are +marked as `const fn` in the `impl`, the user may use the `impl` as the target +of a `const` trait bound which is discussed below. + +## 3. syntactic sugar `const impl` + +Prefixing an `impl` with `const` as in `const impl` is sugar for prefixing all +`fn`s in the `impl`, be it a trait `impl` or an inherent `impl`. As this is +sugar, it adds no additional expressive power to the language, but makes the +use of 2. and existing `const fn` use in inherent `impl`s more ergonomic. + +It also aids searchability by allowing the reader to know directly from the +header that a trait impl is usable as a const trait bound, as opposed to +checking every `fn` for a `const` modifier. + +By doubling as sugar usable for inherent `impl`s, the introduced syntax carries +its own weight. It allows the user to separate `const fn`s and normal `fn`s in +the documentation of inherent `impl`s. + +## 4. `const` trait bounds, `T: const Trait` + +Such a bound `T: const Trait` denotes that `impl Trait for T` must be a +constant trait impl (with only `const fn`s) as suggested in 2-3. In a +`const fn foo(..)`, this fact may be used to call methods +from `Trait` in `foo`. + +It may also be used for `const` and `static` bindings or as input for const +generics inside a normal `fn foo(..)` declaration. And in such +a context, the user can be certain that no I/O may happen inside the called +`const fn`s. When the methods of `Trait` are called with input that is `const`, +the user may also be certain that the call is cheap at runtime. + +The new form of bound also allows reuse of traits, an important step to avoid +a bifurcation of existing traits along the lines of `const fn` vs. `fn`, both +in the standard library and elsewhere. A canonical example that const trait +bounds would solve is not having both `Default` and `ConstDefault`. Doing that +is important because it considerably reduces the amount of duplication of +`impl`s for such traits. + +A consequence of const trait bounds is at least that `F: const FnOnce` is now +possible, allowing the user to effectively expect a `const fn` closure. + +# Vocabulary +[vocabulary]: #vocabulary + +Let's introduce the new terms used in this RFC. + ++ **const impl syntax**, refers to the specific syntax `const impl`. ++ **constant trait impl**, refers to `impl`s of `trait`s where all `fn`s are +marked as `const fn`. ++ **const trait bound**, refers to a trait bound of the form `T: const Trait`. + +# Guide-level explanation +[guide-level-explanation]: #guide-level-explanation + +We will now go through all the points discussed in the summary and explain +what they entail. + +## 1. `const fn` in traits + +Simply put, the following is now allowed: + +```rust +trait Ada { + const fn foo(inputs..) -> return_type; +} +``` + +In other words, `trait`s may now require of their `impl`s that a certain `fn` +be a `const fn`. Naturally, `foo` will now be type-checked as a `const fn`. + +This is of course not specific to this trait or one method. Any trait, +including those with type parameters can now have `const fn`s in them, +and these `const fn`s may have any number of type parameters, parameters, +and any return type. + +## 2. over-constraining `trait` `fn`s as `const fn` in `impl`s + +Consider the `Default` trait: + +```rust +pub trait Default { + fn default() -> Self; +} +``` + +And and some type - for instance: + +```rust +struct Foo(usize); +``` + +As a rustacean, you may now write: + +```rust +impl Default for Foo { + const fn default() -> Self { + Foo(0) + } +} +``` + +Note that this `impl` has constrained `default` more than required by the +`Default` trait. Why this is useful will be made clear in the [motivation] +and in the [guide-level-explanation] of `const` trait bounds. + +Naturally, `default` for `Foo` will now be type-checked as a `const fn`. + +## 3. syntactic sugar `const impl` + + + +## 4. `const` trait bounds, `T: const Trait` + + + + + + +Explain the proposal as if it was already included in the language and you were teaching it to another Rust programmer. That generally means: + +- Explaining the feature largely in terms of examples. +- Explaining how Rust programmers should *think* about the feature, and how it should impact the way they use Rust. It should explain the impact as concretely as possible. +- If applicable, provide sample error messages, deprecation warnings, or migration guidance. +- If applicable, describe the differences between teaching this to existing Rust programmers and new Rust programmers. + +For implementation-oriented RFCs (e.g. for compiler internals), this section should focus on how compiler contributors should think about the change, and give examples of its concrete impact. For policy RFCs, this section should provide an example-driven introduction to the policy, and explain its impact in concrete terms. + +# 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 + +Why should we *not* do this? + +# Rationale and alternatives +[alternatives]: #alternatives + +- Why is this design the best in the space of possible designs? +- What other designs have been considered and what is the rationale for not choosing them? +- What is the impact of not doing this? + +# Unresolved questions +[unresolved]: #unresolved-questions + +- What parts of the design do you expect to resolve through the RFC process before this gets merged? +- What parts of the design do you expect to resolve through the implementation of this feature before stabilization? +- What related issues do you consider out of scope for this RFC that could be addressed in the future independently of the solution that comes out of this RFC? From 8bd25e8287e9355d73bfe5ec23ef892cdb906fed Mon Sep 17 00:00:00 2001 From: Mazdak Date: Wed, 6 Dec 2017 03:48:12 +0100 Subject: [PATCH 2/7] rfc, const_bounds_methods: mostly done --- text/0000-const-bounds-methods.md | 350 ++++++++++++++++++++++++++++-- 1 file changed, 328 insertions(+), 22 deletions(-) diff --git a/text/0000-const-bounds-methods.md b/text/0000-const-bounds-methods.md index 564db2bdd3e..48c2bd305e2 100644 --- a/text/0000-const-bounds-methods.md +++ b/text/0000-const-bounds-methods.md @@ -12,7 +12,7 @@ Allows for: 2. over-constraining trait `fn`s as `const fn` in `impl`s. -3. syntactic sugar `const impl` for `impl`s where all fn:s are const. +3. syntactic sugar `const impl` for `impl`s where all `fn`s are const. 4. `const` bounds as in `T: const Trait` satisfied by `T`s with only `const fn`s in their `impl Trait for T {..}`. @@ -103,13 +103,13 @@ what they entail. Simply put, the following is now allowed: ```rust -trait Ada { - const fn foo(inputs..) -> return_type; +trait Foo { + const fn bar(n: usize) -> usize; } ``` In other words, `trait`s may now require of their `impl`s that a certain `fn` -be a `const fn`. Naturally, `foo` will now be type-checked as a `const fn`. +be a `const fn`. Naturally, `bar` will now be type-checked as a `const fn`. This is of course not specific to this trait or one method. Any trait, including those with type parameters can now have `const fn`s in them, @@ -150,50 +150,356 @@ Naturally, `default` for `Foo` will now be type-checked as a `const fn`. ## 3. syntactic sugar `const impl` +In any inherent `impl` (not an impl of a trait), or an `impl` of a trait, +you may now write: +```rust +const impl MyType { + fn foo() -> usize; + + fn bar(x: usize, y: usize) -> usize; + + // .. +} + +const impl MyTrait for MyType { + fn baz() -> usize; + + fn quux(x: usize, y: usize) -> usize; + + // .. +} +``` + +and have the compiler desugar this for you into: + +```rust +impl MyType { + const fn foo() -> usize; + + const fn bar(x: usize, y: usize) -> usize; + + // .. +} + +impl MyTrait for MyType { + const fn baz() -> usize; + + const fn quux(x: usize, y: usize) -> usize; + + // .. +} +``` + +For the latter case of `const impl MyTrait for MyType`, this always means that +`MyType` may be used to substitute for `T` in a bound like `T: const MyTrait`. + +The compiler will of course now check that the `fn`s are const, and refuse to +compile your program if you lied to the compiler. + +When it comes to migrating existing code to this new model, it is recommended +that you simply start by adding `const` right before your `impl` and see if it +still compiles and then continue this process until all `impl`s that can be +`const impl` are. For those that can't, you can still add `const fn` to some +`fn`s in the `impl`. The standard library will certainely follow this process +in trying to make the standard library as (re)useable as possible. ## 4. `const` trait bounds, `T: const Trait` +Speaking of const trait bounds, what are they? They are simply a bound-modifier +on a bound `T: Trait` denoted as `T: const Trait` with some changed semantics. +What are the semantics? That any type you substitute for `T` must in addition +to impl the trait in question, also do so without any normal `fn`s. Any `fn`s +occuring in the `impl` must be marked as `const fn`. These `impl`s are exactly +those `impl`s that are currently, or would type check as `const impl`. +A `const Trait` bound gives you the power to use all `fn`s in the trait in a +`const` context such as in const generics, `const` bindings and in `const fn`s +in general. +Currently, this RFC also proposes that you be allowed to write `impl const Trait` +and `impl const TraitA + const TraitB`, both for static existential and universal +quantification (return and argument positiion). However, the RFC does not, in +its current form, mandate the addition of syntax like `Box`. +If you try to use a type `MyType` that does not fulfill the `const`ness +requirement of `T: const MyTrait`, then the compiler will greet you with +an error message and refuse to compile your program. -Explain the proposal as if it was already included in the language and you were teaching it to another Rust programmer. That generally means: +Let us now see some, albeit somewhat contrived, examples of `const Trait` bounds. -- Explaining the feature largely in terms of examples. -- Explaining how Rust programmers should *think* about the feature, and how it should impact the way they use Rust. It should explain the impact as concretely as possible. -- If applicable, provide sample error messages, deprecation warnings, or migration guidance. -- If applicable, describe the differences between teaching this to existing Rust programmers and new Rust programmers. +### Static-dispatch existential quantification -For implementation-oriented RFCs (e.g. for compiler internals), this section should focus on how compiler contributors should think about the change, and give examples of its concrete impact. For policy RFCs, this section should provide an example-driven introduction to the policy, and explain its impact in concrete terms. +```rust +fn foo() -> impl const Default + const From<()> { + struct X; + const impl From<()> for X { fn from(_: ()) -> Self { X } } + const impl Default for X { fn default() -> Self { X } } + X +} +``` + +or with alternative and optional syntax: + +```rust +fn foo() -> impl (const Default) + (const From<()>) { + struct X; + const impl From<()> for X { fn from(_: ()) -> Self { X } } + const impl Default for X { fn default() -> Self { X } } + X +} +``` + +### Static-dispatch universal quantification + +```rust +const fn foo(universal: impl const Into) -> usize { + universal.into() +} +``` + +### In a free `fn` + +```rust +fn foo(x: T) -> T { + T::default() + x +} +``` + +### In bounds on type variables of an `impl` and `trait` + +```rust +trait Foo>: Sized { + const fn bar(x: X) -> Self { + x.into() + } +} + +impl Self> Twice { + const fn twice(self, fun: F) -> Self { + fun(fun(self)) + } +} +``` + +We could enumerate a lot more examples - but instead, what you should really +understand is that anywhere you may write `T: SomeTrait`, you may also write: +`T: const SomeTrait`. + +# How do we teach this? + +It should be noted that the concept of a const trait bound is an advanced one. +As such, it will and should not be one of the early topics that an aspiring +rustacean will study. These topics should be taught in conjunction with +and gradually after teaching about free `const fn`s and their inherent siblings. +However, it should be noted that a user that only knows of `fn` and has never +heard of `const fn` may still happily and obliviously use a `const impl` or +overconstrained `const fn`s in impls just as they can with free `const fn`s +today. # Reference-level explanation [reference-level-explanation]: #reference-level-explanation -This is the technical portion of the RFC. Explain the design in sufficient detail that: +This RFC entails: + +1. `const fn` in `trait`s. + +2. over-constraining trait `fn`s as `const fn` in `impl`s. + +3. syntactic sugar `const impl` for `impl`s where all `fn`s are const. + +4. `const` bounds as in `T: const Trait` satisfied by `T`s with only +`const fn`s in their `impl Trait for T {..}`. -- Its interaction with other features is clear. -- It is reasonably clear how the feature would be implemented. -- Corner cases are dissected by example. +Let us unpack each of this one by one and go through their mechanics and +cooperation. -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. `const fn`s in `trait`s + +The syntactic rules of `trait` (now) allow `fn`s to be optionally prefixed +with `const`. + +Semantically, a `const fn` inside a `trait MyTrait` requires that any `impl` +for some type of the trait include that `fn`, and that it must be `const fn`. +Such trait-mandated `const fn`s are obviously type-checked as `const fn` also. +Unlike free `const fn`s, those in traits may refer to `self`, `Self`, +associated types, and constants, syntactically. Type checking (now) takes this +into account. + +A simple example of `const fn`s in a trait is: + +```rust +pub trait Foo { + const fn bar(x: usize, y: usize) -> Self; + + const fn baz(self) -> usize; +} +``` + +## 2. over-constraining trait `fn`s as `const fn` in `impl`s + +This entails that any trait `impl` may constrain an `fn` in the trait as +`const fn`. This means that the `impl` is voluntarily opting in to a +being more restrictions on the `fn`s than the trait required. Other than +knowing that certain things which `const fn`s forbid are now not used for +that `fn`, there are other benefits to opting in. The `impl` may opt-in +for as many trait `fn`s as it likes - zero, one, .. or even all. +Those over-constrained `fn`s are now type-checked as `const fn`s. + +## 3. syntactic sugar `const impl` + +Rust (now) allows the user to prefix `impl` with `const` as in this example: + +```rust +const impl Foo { + fn bar() -> usize; + + fn baz(x: usize, y: usize) -> usize; + + // .. +} + +const impl Wibble for Wobble { + fn wubble() -> usize; + + fn quux(x: usize, y: usize) -> usize; + + // .. +} +``` + +The compiler will desugar the above `impl`s to: + +```rust +impl Foo { + const fn bar() -> usize; + + const fn baz(x: usize, y: usize) -> usize; + + // .. +} + +impl Wibble for Wobble { + const fn wubble() -> usize; + + const fn quux(x: usize, y: usize) -> usize; + + // .. +} +``` + +## 4. `const` trait bounds, `T: const Trait` + +Introduced syntax: in addition to `$ident: $ident` allowed in `where` clauses +and where type variables are introduced as in for example `impl< $here >` you +may (now) write `$ident: const $ident`. This is called a const trait bound. +An example of such a bound is `F: const FnOnce(X) -> Y` as well as +`D: const Default`. + +Semantically, having such a bound means that when a type `MyType` replaces a +type variable with that bound, it may only do so iff there exists an `impl` of +the trait for the type that is also a constant `impl`. + +What is a constant impl? For an `impl` to be considered constant, the only `fn`s +it may have are `const fn`s. This coincides with the `const impl` syntax, +in other words: `const impl` syntax introduces a constant impl. The user may +however manually prefix `const` before every method and have a valid constant +impl still. + +Since a `T: const Trait` bound entails that any `::method` be a +`const fn`. This further entails that `method` may be used within a `const fn`, +and other `const` contexts such as const-generics, defining the value of +an associated consttant, etc. + +### Type checking + +We give a high level description of an algorithm for type checking this idea. + +During registration and collection of `impl`s, a check is done whether all `fn`s +are prefixed with `const`. This is a purely syntactic check. A flag `is_const` +is then stored on/associated-with the `impl`. Checking that bodies of the +methods actually follow the rules of `const fn` can now be done separately. + +During type checking of a `const fn`, iff a bound `T: const Trait` exists, +then the type checking allows the use of `T::trait_method()` inside. This also +applies to bounds on `impl` which allows associated constants to use such +functions in their definition site. + +During unification/substitution of type variables for specific/concrete types, +a lookup is first done to see if the impl exists. So far so normal. If a const +trait bound exists, then the `is_const` (which is by default false) flag is +checked for `true`. If it is `true`, then the type is substituted. Otherwise, +a typeck error is raised. # Drawbacks [drawbacks]: #drawbacks -Why should we *not* do this? +- This is quite a large addition to the language both semantically and +syntactically. Some users may wish for a smaller language with fewer features. + +- The syntax `T: const Trait` may be confused with `const T: usize`. + +- The usefulness of `const impl` can be called into question. However it +may pull its own weight especially during migration. # Rationale and alternatives [alternatives]: #alternatives -- Why is this design the best in the space of possible designs? -- What other designs have been considered and what is the rationale for not choosing them? -- What is the impact of not doing this? +The impact of not using the ideas at the core of the RFC is loss of +expressive power. + +With regards to const trait bounds, we trait system could simply be bifurcated. +Have one trait for the const version, and one for the normal. This does however +not scale. It is much better to have a modifier on bounds than many more traits. +The RFC argues therefore that this design is better compared to introducing +traits such as: + +```rust +pub trait ConstDefault: Default { const DEFAULT: Self; } +``` + +If part 1. of this RFC is not merged, only `fn`s of the form `() -> RegularType` +may even be used, so full bifurcation would not even be possible then. + +The natural companions to const trait bounds are constant impls, without them, +that part of the proposal wouldn't be nearly as expressive. + +We can ask why const trait bounds impose an all-or-nothing proposition and why +the user is not just obliged to satisfy constness of those particular `fn`s that +the user of the bound uses. This is due to the fragility of such a system. +The const trait bound is morally right to at any time use more `fn`s from the +repertoire of `fn`s at their disposal. But if all `fn`s were not required to +be `const` and the call site provided a type which only satisfied constness for +one `fn`, then the type-checker will all of a sudden refuse to give its go-ahead +and as a result, the program refuses to compile and you have a backwards compatibility breakage. + +We could of course solve this by requiring the bound to specify exactly what +`fn`s are required to be `const`. In practice, this would be tedious to write +since there may be more than one `fn`. It is therefore a recipe for bad +ergonomics and developer experience. # Unresolved questions [unresolved]: #unresolved-questions -- What parts of the design do you expect to resolve through the RFC process before this gets merged? -- What parts of the design do you expect to resolve through the implementation of this feature before stabilization? -- What related issues do you consider out of scope for this RFC that could be addressed in the future independently of the solution that comes out of this RFC? +- We should decide on the interaction of const trait bounds with existential +types, both dynamic (trait objects) and static (`impl Trait`). Should the user +be able to say for example `Box` and `impl const Foo` (perhaps +instead with the syntax: `impl (const Foo)`)? + +- Therefore: Are "const trait objects" sound? + +- We must be reasonably confident of the soundness of this proposal. +Some edge cases may still be resolvable prior to stabilization. + +- Should we consider the syntax `const trait Foo { .. }`? The current thinking +is that it would not carry its weight. It is much more common to define `impl`s +than `trait`s! + +# Acknowledgements +[acknowledgements]: #acknowledgements + +This RFC was significantly improved by exhaustive and deep discussions with +fellow rustaceans Ixrec, Alexander "durka" Burka, rkruppe, and Eduard-Mihai +"eddyb" Burtescu. I would like thank you for being excellent and considerate +people. \ No newline at end of file From c3494906ace64ed0aad8dbf2dfb2829f5d5a53e0 Mon Sep 17 00:00:00 2001 From: Mazdak Date: Wed, 6 Dec 2017 17:15:01 +0100 Subject: [PATCH 3/7] rfc, const_bounds_methods: more on impl trait, specialization --- text/0000-const-bounds-methods.md | 75 +++++++++++++++++++++++++++++-- 1 file changed, 71 insertions(+), 4 deletions(-) diff --git a/text/0000-const-bounds-methods.md b/text/0000-const-bounds-methods.md index 48c2bd305e2..d14fb129bb3 100644 --- a/text/0000-const-bounds-methods.md +++ b/text/0000-const-bounds-methods.md @@ -424,7 +424,8 @@ methods actually follow the rules of `const fn` can now be done separately. During type checking of a `const fn`, iff a bound `T: const Trait` exists, then the type checking allows the use of `T::trait_method()` inside. This also applies to bounds on `impl` which allows associated constants to use such -functions in their definition site. +functions in their definition site. For normal `fn`s with a const trait bound, +any `const` bindings inside the `fn` are now allowed to use `T::trait_method()`. During unification/substitution of type variables for specific/concrete types, a lookup is first done to see if the impl exists. So far so normal. If a const @@ -432,6 +433,65 @@ trait bound exists, then the `is_const` (which is by default false) flag is checked for `true`. If it is `true`, then the type is substituted. Otherwise, a typeck error is raised. +## 4.1 Static-dispatch existential types (`impl Trait`) + +Introduced syntax: In addition to normal (static-dispatch) existential type +syntax `-> impl Trait` or `-> impl TraitA + TraitB + ..` you may (now) also +write `-> impl const Trait` or `-> impl const TraitA + traitB + const TraitC`. +To aid reading, grouping with parenthesis is possible as: +`-> impl (const Trait)` or `-> impl (const TraitA) + traitB + (const TraitC)`. + +As expected, `-> impl const Trait` entails that the returned type now must, +in addition to providing an `impl` of the `Trait` for the returned anonymous +type now also do so by having the impl be constant trait impl as done in +the following example: + +```rust +fn foo() -> impl const Default + const From<()> { + struct X; + const impl From<()> for X { fn from(_: ()) -> Self { X } } + const impl Default for X { fn default() -> Self { X } } + X +} +``` + +### Type checking + +A caller of `foo()` may use the result where a universally quantified bound +`T: const Default + const From<()>` exists and use the methods of `Default` +and `From<()>` in a `const` context: `const fn`, associated consts, +const bindings, and array length size. + +## 4.2 Static-dispatch universal quantification + +Introduced syntax: In addition to normal anonymous (static-dispatch) universally +quantified type syntax `argument: impl Trait`, you may (now) also write: +`argument: -> impl const Trait` as in the following example: + +```rust +const fn foo(universal: impl const Into) -> usize { + universal.into() +} +``` + +### Type checking + +In the above example, the behaviour is identical to: + +```rust +const fn foo>(universal: T) -> usize { + universal.into() +} +``` + +and treated as such for type-checking purposes. +The only difference is that the type `T` is anonymous and may now +not be reused other than by type alias. + +## 4.3 `impl trait` type alias + +Introduced syntax: As you may write `type Foo = impl Trait;` you may (now) also write: `type Foo = impl const Trait`. + # Drawbacks [drawbacks]: #drawbacks @@ -443,6 +503,10 @@ syntactically. Some users may wish for a smaller language with fewer features. - The usefulness of `const impl` can be called into question. However it may pull its own weight especially during migration. +- This will lead to increased compile times, but due to semantic compression +(less code for more intent), compile times can also increase less than it would +with bifurcation of the trait system. Const trait bounds are cheap to check. + # Rationale and alternatives [alternatives]: #alternatives @@ -482,6 +546,9 @@ ergonomics and developer experience. # Unresolved questions [unresolved]: #unresolved-questions +- We must be reasonably confident of the soundness of this proposal. +Some edge cases may still be resolvable prior to stabilization. + - We should decide on the interaction of const trait bounds with existential types, both dynamic (trait objects) and static (`impl Trait`). Should the user be able to say for example `Box` and `impl const Foo` (perhaps @@ -489,13 +556,13 @@ instead with the syntax: `impl (const Foo)`)? - Therefore: Are "const trait objects" sound? -- We must be reasonably confident of the soundness of this proposal. -Some edge cases may still be resolvable prior to stabilization. - - Should we consider the syntax `const trait Foo { .. }`? The current thinking is that it would not carry its weight. It is much more common to define `impl`s than `trait`s! +- Does `T: const Trait` specialize `T: Trait`? We can always initially answer +no to this and then change during stabilization as power is strictly gained. + # Acknowledgements [acknowledgements]: #acknowledgements From 9723a2ae380971987f33836c1ef248e35fb101e2 Mon Sep 17 00:00:00 2001 From: Mazdak Date: Wed, 6 Dec 2017 18:20:40 +0100 Subject: [PATCH 4/7] rfc, const_bounds_methods: details on const trait objects --- text/0000-const-bounds-methods.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/text/0000-const-bounds-methods.md b/text/0000-const-bounds-methods.md index d14fb129bb3..236c0b5abb1 100644 --- a/text/0000-const-bounds-methods.md +++ b/text/0000-const-bounds-methods.md @@ -549,12 +549,12 @@ ergonomics and developer experience. - We must be reasonably confident of the soundness of this proposal. Some edge cases may still be resolvable prior to stabilization. -- We should decide on the interaction of const trait bounds with existential -types, both dynamic (trait objects) and static (`impl Trait`). Should the user -be able to say for example `Box` and `impl const Foo` (perhaps -instead with the syntax: `impl (const Foo)`)? - -- Therefore: Are "const trait objects" sound? +- We should decide on the interaction of const trait bounds with trait objects. +Should the user be able to say for example `Box`? While very little +expressive power is gained through this (you get to restrict what `&(const Foo)` +may do by only allowing calls to `const fn`s), `&'static (const Foo)` may be +more useful - and then, for the sake of not special casing, `Box` +should be allowed. - Should we consider the syntax `const trait Foo { .. }`? The current thinking is that it would not carry its weight. It is much more common to define `impl`s From afa3850d8c2a27b8ee8cd9fb9ba5e415e57f7360 Mon Sep 17 00:00:00 2001 From: Mazdak Date: Wed, 6 Dec 2017 18:45:03 +0100 Subject: [PATCH 5/7] rfc, const_bounds_methods: fix typos, thanks durka =) --- text/0000-const-bounds-methods.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text/0000-const-bounds-methods.md b/text/0000-const-bounds-methods.md index 236c0b5abb1..99f6d4c0c98 100644 --- a/text/0000-const-bounds-methods.md +++ b/text/0000-const-bounds-methods.md @@ -22,7 +22,7 @@ Allows for: This RFC adds a non-trivial amount of expressive power to the language. -Let us go throw the motivation for each bit in order. +Let us go through the motivation for each bit in order. ## 1. `const fn` in traits @@ -567,6 +567,6 @@ no to this and then change during stabilization as power is strictly gained. [acknowledgements]: #acknowledgements This RFC was significantly improved by exhaustive and deep discussions with -fellow rustaceans Ixrec, Alexander "durka" Burka, rkruppe, and Eduard-Mihai +fellow rustaceans Ixrec, Alex "durka" Burka, rkruppe, and Eduard-Mihai "eddyb" Burtescu. I would like thank you for being excellent and considerate people. \ No newline at end of file From 3aa1597976eccf6189ce9b3c4b5479a9c8f4dbfd Mon Sep 17 00:00:00 2001 From: Mazdak Date: Wed, 6 Dec 2017 18:53:11 +0100 Subject: [PATCH 6/7] rfc, const_bounds_methods: improved summary --- text/0000-const-bounds-methods.md | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/text/0000-const-bounds-methods.md b/text/0000-const-bounds-methods.md index 99f6d4c0c98..1f629b09990 100644 --- a/text/0000-const-bounds-methods.md +++ b/text/0000-const-bounds-methods.md @@ -10,12 +10,19 @@ Allows for: 1. `const fn` in `trait`s. -2. over-constraining trait `fn`s as `const fn` in `impl`s. +2. over-constraining trait `fn`s as `const fn` in `impl`s. This means that +you may write `const fn foo(x: usize) -> usize {..}` in the impl even tho the +trait only required `fn foo(x: usize) -> usize {..}`. -3. syntactic sugar `const impl` for `impl`s where all `fn`s are const. +3. syntactic sugar `const impl` for `impl`s which is desugared by prefixing +all `fn`s with `const`. 4. `const` bounds as in `T: const Trait` satisfied by `T`s with only -`const fn`s in their `impl Trait for T {..}`. +`const fn`s in their `impl Trait for T {..}`. This means that for any +concrete `MyType`, you may only substitute it for `T: const Trait` iff +`impl MyType for Trait { .. }` exists and the only `fn` items inside +are `const fn`s. Writing `const impl MyType for Trait { .. }` +satisfies this. # Motivation [motivation]: #motivation From 0068ba5e887c710498faa60f1492b01375484550 Mon Sep 17 00:00:00 2001 From: Mazdak Date: Wed, 6 Dec 2017 20:52:04 +0100 Subject: [PATCH 7/7] rfc, const_bounds_methods: notes on Box, -> const impl Trait syntax, type inference alt. --- text/0000-const-bounds-methods.md | 48 +++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/text/0000-const-bounds-methods.md b/text/0000-const-bounds-methods.md index 1f629b09990..c47d6c56567 100644 --- a/text/0000-const-bounds-methods.md +++ b/text/0000-const-bounds-methods.md @@ -230,6 +230,14 @@ and `impl const TraitA + const TraitB`, both for static existential and universa quantification (return and argument positiion). However, the RFC does not, in its current form, mandate the addition of syntax like `Box`. +The syntax `Box` might seem a bit strange - what would it mean were +it allowed, semantically? It means that if you have a `bar: T` where `T: Foo`, +you can only do `Box::new(bar)` iff `T: const Foo` as well. This means that you +can ensure that a reduced subset of Rust is used when using the dereferenced +`&(const Foo)`, namely `const fn`s. The value of `Box::new(foo)` is not `const` itself tho. + +What does `Box` mean? It means that + If you try to use a type `MyType` that does not fulfill the `const`ness requirement of `T: const MyTrait`, then the compiler will greet you with an error message and refuse to compile your program. @@ -550,6 +558,38 @@ We could of course solve this by requiring the bound to specify exactly what since there may be more than one `fn`. It is therefore a recipe for bad ergonomics and developer experience. +As an alternative to const trait bounds, type inference could be used to infer +if a function can be used to executed in constant time or not. This however is +less explicit and incurs the cost of increased complexity of type inference +which may negatively impact Rusts already long compile times. In this context +it should also be noted that the following would not be possible with type +inference: + +```rust +fn foo(bar: T) { + // Use the const Default bound: + // Type inference can not help us here as there is no way to ensure that + // T::default() is const. + const BAZ: T = T::default(); + + // Use the iterator bound: + BAZ.foreach(|elt| { dbg!(elt); }); +} +``` + +but is possible with this RFC as in: + +```rust +fn foo(bar: T) { + // Use the const Default bound: + // We can ensure that T::default() is const. + const BAZ: T = T::default(); + + // Use the iterator bound: + BAZ.foreach(|elt| { dbg!(elt); }); +} +``` + # Unresolved questions [unresolved]: #unresolved-questions @@ -570,6 +610,14 @@ than `trait`s! - Does `T: const Trait` specialize `T: Trait`? We can always initially answer no to this and then change during stabilization as power is strictly gained. +- Should we use the syntax `arg: const impl Trait` and `-> const impl Trait` +instead of `arg: impl const Trait` and `-> impl const Trait`? This reads better +with `const impl` sugar syntax, but removes the ability to say things such as +`-> impl TraitA + const TraitB`. We could also allow both syntaxes, one for +increased readability in the majority of cases (no mixing of `const` and not), +and the other to have expressive power retained. A possibility for regaining +expressivity is `-> const impl TraitA + impl TraitB`. + # Acknowledgements [acknowledgements]: #acknowledgements