From ac64358d5c48869e1b536725782617900e7cd191 Mon Sep 17 00:00:00 2001 From: Without Boats Date: Thu, 11 May 2017 15:19:02 -0700 Subject: [PATCH 01/12] Const generics. --- text/0000-const-generics.md | 287 ++++++++++++++++++++++++++++++++++++ 1 file changed, 287 insertions(+) create mode 100644 text/0000-const-generics.md diff --git a/text/0000-const-generics.md b/text/0000-const-generics.md new file mode 100644 index 00000000000..d9751a38c3e --- /dev/null +++ b/text/0000-const-generics.md @@ -0,0 +1,287 @@ +- Feature Name: const_generics +- Start Date: 2017-05-01 +- RFC PR: (leave this empty) +- Rust Issue: (leave this empty) + +# Summary +[summary]: #summary + +Allow types to be generic over constant values; among other things this will +allow users to write impls which are abstract over all array types. + +# Motivation +[motivation]: #motivation + +Rust currently has one type which is parametric over constants: the built-in +array type `[T; LEN]`. However, because const generics are not a first class +feature, users cannot define their own types which are generic over constant +values, and cannot implement traits for all arrays. + +As a result of this limitation, the standard library only contains trait +implementations for arrays up to a length of 32; as a result, arrays are often +treated as a second-class language feature. Even if the length of an array +might be statically known, it is more common to heap allocate it using a +vector than to use an array type (which has certain performance trade offs). + +Const parameters can also be used to allow users to more naturally specify +variants of a generic type which are more accurately reflected as values, +rather than types. For example, if a type takes a name as a parameter for +configuration or other reasons, it may make more sense to take a `&'static str` +than take a unit type which provides the name (through an associated const or +function). This can simplify APIs. + +Lastly, consts can be used as parameters to make certain values determined at +typecheck time. By limiting which values a trait is implemented over, the +orphan rules can enable a crate to ensure that only some safe values are used, +with the check performed at compile time (this is especially relevant to +cryptographic libraries for example). + +# Detailed design +[design]: #detailed-design + +Today, types in Rust can be parameterized by two kinds: types and lifetimes. We +will additionally allow types to be parameterized by values, so long as those +values can be computed at compile time. A single constant parameter must be of +a single, particular type, and can be validly substituted with any value of +that type which can be computed at compile time and the type meets the equality +requirements laid out later in this RFC. + +(Exactly which expressions are evaluable at compile time is orthogonal to this +RFC. For our purposes we assume that integers and their basic arithmetic +operations can be computed at compile time, and we will use them in all +examples.) + +## Glossary + +* __Const (constant, const value):__ A Rust value which is guaranteed to be +fully evaluated at compile time. Unlike statics, consts will be inlined at +their use sites rather than existing in the data section of the compiled +binary. + +* __Const parameter (generic const):__ A const which a type or function is +abstract over; this const is input to the concrete type of the item, such as +the length parameter of a static array. + +* __Associated const:__ A const associated with a trait, similar to an +associated type. Unlike a const parameter, an associated const is *determined* +by a type. + +* __Const variable:__ Either a const parameter or a trait, contrast with +concrete const; a const which is undetermined in this context (prior to +monomorphization). + +* __Concrete const:__ In contrast to a const variable, a const which has a +known and singular value in this context. + +* __Const expression:__ An expression which evaluates to a const. This may be +an identity expression or a more complex expression, so long as it can be +evaluated by Rust's const system. + +* __Abstract const expression:__ A const expression which involves a const +variable (and therefore the value that it evaluates to cannot be determined +until after monomorphization). + +* __Const projection:__ The value of an abstract const expression (which cannot +be determined in a generic context because it is dependent on a const +variable). + +* __Identity expression:__ An expression which cannot be evaluated further +except by substituting it with names in scope. This includes all literals as +well all idents - e.g. `3`, `"Hello, world"`, `foo_bar`. + +## Declaring a const parameter + +In any sequence of type parameter declarations - such as in the definition of a +type or on the `impl` header of an impl block - const parameters can also be +declared. Const parameters always come after type parameters, and their +declarations take the form `const $ident: $ty`, as in: + +```rust +struct RectangularArray { + array: [[T; WIDTH]; HEIGHT], +} +``` + +The idents declared are the names used for these const parameters +(interchangeably called "const variables" in this RFC text), and all values +must be of the type ascribed to it. Which types can be ascribed to const +parameters is restricted later in this RFC. + +The const parameter is in scope for the entire body of the item (type, impl, +function, method, etc) in which it is declared. + +## Applying a const as a parameter + +Any const expression of the type ascribed to a const parameter can be applied +as that parameter. When applying an expression as const parameter (except for +arrays), which is not an identity expression, the expression must be contained +within a block. This syntactic restriction is necessary to avoid requiring +infinite lookahead when parsing an expression inside of a type. + +```rust +const X: usize = 7; + +let x: RectangularArray; +let y: RectangularArray; +``` + +### Arrays +Arrays have a special construction syntax: `[T; CONST]`. In array syntax, +braces are not needed around any const expressions; `[i32; N * 2]` is a +syntactically valid type. + +## When a const variable can be used + +A const variable can be used as a const in any of these contexts: + +1. As an applied const to any type which forms a part of the signature of +the item in question: `fn foo(arr: [i32; N])`. +2. As part of a const expression used to define an associated const, or as a +parameter to an associated type. +3. As a value in any runtime expression in the body of any functions in the +item. +4. As a parameter to any type used in the body of any functions in the item, +as in `let x: [i32; N]` or `<[i32; N] as Foo>::bar()`. +5. As a part of the type of any fields in the item (as in +`struct Foo([i32; N]);`). + +In general, a const variable can be used where a const can. There is one +significant exception: const variables cannot be used in the construction of +consts, statics, functions, or types inside a function body. That is, these +are invalid: + +```rust +fn foo() { + const Y: usize = X * 2; + static Z: (usize, usize)= (X, X); + + struct Foo([i32; X]); +} +``` + +This restriction can be analogized to the restriction on using type variables +in types constructed in the body of functions - all of these declarations, +though private to this item, must be independent of it, and do not have any +of its parameters in scope. + +## Theory of equality for type equality of two consts + +During unification and the overlap check, it is essential to determine when two +types are equivalent or not. Because types can now be dependent on consts, we +must define how we will compare the equality of two constant expressions. + +For most cases, the equality of two consts follows the same reasoning you would +expect - two constant values are equal if they are equal to one another. But +there are some particular caveats. + +### Structural equality + +Const equality is determined according to the definition of structural equality +defined in [RFC 1445][1445]. Only types which have the "structural match" +property can be used as const parameters. This would exclude floats, for +example. + +The structural match property is intended as a stopgap until a final solution +for matching against consts has been arrived at. It is important for the +purposes of type equality that whatever solution const parameters use will +guarantee that the equality is *reflexive*, so that a type is always the same +type as itself. (The standard definition of equality for floating point numbers +is not reflexive.) + +This may diverse someday from the definition used by match; it is not necessary +that matching and const parameters use the same definition of equality, but the +definition of equality used by match today is good enough for our purposes. + +Because consts must have the structural match property, and this property +cannot be enforced for a type variable, it is not possible to introduce a const +parameter which is ascribed to a type variable (`` is not +valid)> + +### Equality of two abstract const expressions + +When comparing the equality of two abstract const expressions (that is, those +that depend on a variable) we cannot compare the equality of their values +because their values are determined by an const variable, the value of which is +unknown prior to monomorphization. + +For this reason we will (initially, at least) treat the return value of const +expressions as *projections* - values determined by the input, but which are +not themselves known. This is similar to how we treat associated types today. +When comparing the evaluation of an abstract const expression - which we'll +call a *const projection* - to another const of the same type, its equality is +always unknown. + +Therefore we can neither unify nor guarantee the nonunification of any const +projection with any other const unless they are *syntactically identical.* That +is, because we require that const equality is reflexive, we know that `{N + 1}` +is equal to `{N + 1}`, but we don't know whether or not it is equal to +`{N * 2}` or even to `N` or `{1 + N}`. + +#### Future extensions + +Someday we could introduce knowledge of the basic properties of some operations +- such as the commutitivity of addition and multiplication - to begin making +smarter judgments on the equality of const projections. However, this RFC does +not proposing building any knowledge of that sort into the language and doing +so would require a future RFC. + +## Specialization on const parameters + +It is also necessary for specialization that const parameters have a defined +ordering of specificity. For this purpose, literals are defined as more +specific than other expressions, otherwise expressions have an indeterminate +ordering. + +Just as we could some day support more advanced notions of equality between +const projections, we could some day support more advanced definitions of +specificity. For example, given the type `(i32, i32)`, we could determine that +`(0, PARAM2)` is more specific than `(PARAM1, PARAM2)` - roughly the analog +of understanding that `(i32, U)` is more specific than the type `(T, U)`. We +could also someday support intersectional and other more advanced definitions +of specialization on constants. + +# How We Teach This +[how-we-teach-this]: #how-we-teach-this + +Const generics is a large feature, and will require significant educational +materials - it will need to be documented in both the book and the reference, +and will probably need its own section in the book. Documenting const generics +will be a big project in itself. + +However, const generics should be treated as an advanced feature, and it should +not be something we expose to new users early in their use of Rust. + +# Drawbacks +[drawbacks]: #drawbacks + +This feature adds a significant amount of complexity to the type system, +allowing types to be determined by constants. It requires determining the rules +around abstract const equality, which result in surprising edge cases. It adds +a lot of syntax to the language. The language would definitely be simpler if we +don't adopt this feature. + +However, we have already introduced a type which is determined by a constant - +the array type. Generalizing this feature seems natural and even inevitable +given that early decision. + +# Alternatives +[alternatives]: #alternatives + +There are not really alternatives other than not doing this, or staging it +differently. + +We could limit const generics to the type `usize`, but this would not make the +implementation simpler. + +We could move more quickly to more complex notions of equality between consts, +but this would make the implementation more complex up front. + +We could choose a slightly different syntax, such as separating consts from +types with a semicolon. + +# Unresolved questions +[unresolved]: #unresolved-questions + +None known at this time (at least, none recalled). + +[1445]: https://github.com/rust-lang/rfcs/blob/master/text/1445-restrict-constants-in-patterns.md From 90cbc6de5bb2f9b5c5c8ff982fc0122ae3b3e72b Mon Sep 17 00:00:00 2001 From: Without Boats Date: Thu, 11 May 2017 15:25:11 -0700 Subject: [PATCH 02/12] Fix markdown? --- text/0000-const-generics.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/text/0000-const-generics.md b/text/0000-const-generics.md index d9751a38c3e..1ce77831e0b 100644 --- a/text/0000-const-generics.md +++ b/text/0000-const-generics.md @@ -91,9 +91,9 @@ well all idents - e.g. `3`, `"Hello, world"`, `foo_bar`. ## Declaring a const parameter -In any sequence of type parameter declarations - such as in the definition of a -type or on the `impl` header of an impl block - const parameters can also be -declared. Const parameters always come after type parameters, and their +In any sequence of type parameter declarations -- such as in the definition of +a type or on the `impl` header of an impl block -- const parameters can also +be declared. Const parameters always come after type parameters, and their declarations take the form `const $ident: $ty`, as in: ```rust From 2ec7001d0bd4690b5c07b4abc89a429406b8beb2 Mon Sep 17 00:00:00 2001 From: Without Boats Date: Thu, 11 May 2017 15:26:37 -0700 Subject: [PATCH 03/12] Fix markdown? (take 2) --- text/0000-const-generics.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/text/0000-const-generics.md b/text/0000-const-generics.md index 1ce77831e0b..0d7fe2d9df7 100644 --- a/text/0000-const-generics.md +++ b/text/0000-const-generics.md @@ -91,9 +91,9 @@ well all idents - e.g. `3`, `"Hello, world"`, `foo_bar`. ## Declaring a const parameter -In any sequence of type parameter declarations -- such as in the definition of -a type or on the `impl` header of an impl block -- const parameters can also -be declared. Const parameters always come after type parameters, and their +In any sequence of type parameter declarations (such as in the definition of a +type or on the `impl` header of an impl block) const parameters can also be +declared. Const parameters always come after type parameters, and their declarations take the form `const $ident: $ty`, as in: ```rust From 2119312320eb28e35334ba2d00cce8db174518d1 Mon Sep 17 00:00:00 2001 From: Without Boats Date: Thu, 11 May 2017 15:55:33 -0700 Subject: [PATCH 04/12] Fix diverse/diverge typo. --- text/0000-const-generics.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-const-generics.md b/text/0000-const-generics.md index 0d7fe2d9df7..be860cdae45 100644 --- a/text/0000-const-generics.md +++ b/text/0000-const-generics.md @@ -188,7 +188,7 @@ guarantee that the equality is *reflexive*, so that a type is always the same type as itself. (The standard definition of equality for floating point numbers is not reflexive.) -This may diverse someday from the definition used by match; it is not necessary +This may diverge someday from the definition used by match; it is not necessary that matching and const parameters use the same definition of equality, but the definition of equality used by match today is good enough for our purposes. From 5cd2118971c61b1c70fc8db2baa26c6af111e659 Mon Sep 17 00:00:00 2001 From: Without Boats Date: Thu, 11 May 2017 15:56:04 -0700 Subject: [PATCH 05/12] Make psuedocode clearer. --- text/0000-const-generics.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-const-generics.md b/text/0000-const-generics.md index be860cdae45..e875ba9e0f1 100644 --- a/text/0000-const-generics.md +++ b/text/0000-const-generics.md @@ -194,7 +194,7 @@ definition of equality used by match today is good enough for our purposes. Because consts must have the structural match property, and this property cannot be enforced for a type variable, it is not possible to introduce a const -parameter which is ascribed to a type variable (`` is not +parameter which is ascribed to a type variable (`Foo` is not valid)> ### Equality of two abstract const expressions From 122fab1c3515d6df30b6da035588f91d90b383e2 Mon Sep 17 00:00:00 2001 From: Without Boats Date: Thu, 11 May 2017 15:56:58 -0700 Subject: [PATCH 06/12] Fix typo. --- text/0000-const-generics.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-const-generics.md b/text/0000-const-generics.md index e875ba9e0f1..afe48d827aa 100644 --- a/text/0000-const-generics.md +++ b/text/0000-const-generics.md @@ -195,7 +195,7 @@ definition of equality used by match today is good enough for our purposes. Because consts must have the structural match property, and this property cannot be enforced for a type variable, it is not possible to introduce a const parameter which is ascribed to a type variable (`Foo` is not -valid)> +valid). ### Equality of two abstract const expressions From 5a383d5b867f542a9fc75a9c8cc40f9e65837549 Mon Sep 17 00:00:00 2001 From: Without Boats Date: Thu, 11 May 2017 18:59:45 -0700 Subject: [PATCH 07/12] More typos. --- text/0000-const-generics.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/text/0000-const-generics.md b/text/0000-const-generics.md index afe48d827aa..95d2e1fcecd 100644 --- a/text/0000-const-generics.md +++ b/text/0000-const-generics.md @@ -177,7 +177,7 @@ there are some particular caveats. ### Structural equality Const equality is determined according to the definition of structural equality -defined in [RFC 1445][1445]. Only types which have the "structural match" +defined in [RFC 1445][1445]. Only types which have the "structural match" property can be used as const parameters. This would exclude floats, for example. @@ -201,7 +201,7 @@ valid). When comparing the equality of two abstract const expressions (that is, those that depend on a variable) we cannot compare the equality of their values -because their values are determined by an const variable, the value of which is +because their values are determined by a const variable, the value of which is unknown prior to monomorphization. For this reason we will (initially, at least) treat the return value of const @@ -220,7 +220,7 @@ is equal to `{N + 1}`, but we don't know whether or not it is equal to #### Future extensions Someday we could introduce knowledge of the basic properties of some operations -- such as the commutitivity of addition and multiplication - to begin making +- such as the commutativity of addition and multiplication - to begin making smarter judgments on the equality of const projections. However, this RFC does not proposing building any knowledge of that sort into the language and doing so would require a future RFC. From 5afdf55fead3ac758feff6831e28edca8472f789 Mon Sep 17 00:00:00 2001 From: Without Boats Date: Fri, 1 Sep 2017 10:58:06 -0700 Subject: [PATCH 08/12] Update RFC. - Remove claims about syntactic identity, replace with more conservative choice - Add unresolved questions --- text/0000-const-generics.md | 50 ++++++++++++++++++++++++++++++++----- 1 file changed, 44 insertions(+), 6 deletions(-) diff --git a/text/0000-const-generics.md b/text/0000-const-generics.md index 95d2e1fcecd..fcb6064026e 100644 --- a/text/0000-const-generics.md +++ b/text/0000-const-generics.md @@ -211,11 +211,34 @@ When comparing the evaluation of an abstract const expression - which we'll call a *const projection* - to another const of the same type, its equality is always unknown. -Therefore we can neither unify nor guarantee the nonunification of any const -projection with any other const unless they are *syntactically identical.* That -is, because we require that const equality is reflexive, we know that `{N + 1}` -is equal to `{N + 1}`, but we don't know whether or not it is equal to -`{N * 2}` or even to `N` or `{1 + N}`. +Each const expression generates a new projection, which is inherently +anonymous. It is not possible to unify two anonymous projections (imagine two +associated types on a generic - `T::Assoc` and `T::Item`: you can't prove or +disprove that they are the same type). For this reason, const expressions do +not unify with one another unless they are *literally references to the same +AST node*. That means that one instance of `N + 1` does not unify with another +instance of `N + 1` in a type. + +To be clearer, this does not typecheck, because `N + 1` appears in two +different types: + +```rust +fn foo() -> [i32; N + 1] { + let x: [i32; N + 1] = [0; N + 1]; + x +} +``` + +But this does, because it appears only once: + +```rust +type Foo = [i32; N + 1]; + +fn foo() -> Foo { + let x: Foo = [i32; N + 1]; + x +} +``` #### Future extensions @@ -282,6 +305,21 @@ types with a semicolon. # Unresolved questions [unresolved]: #unresolved-questions -None known at this time (at least, none recalled). +- **Unification of abstract const expressions:** This RFC performs the most + minimal unification of abstract const expressions possible - it essentially + doesn't unify them. Possibly this will be an unacceptable UX for + stabilization and we will want to perform some more advanced unification + before we stabilize this feature. +- **Well formedness of const expressions:** Types should be considered well + formed only if during monomorphization they will not panic. This is tricky + for overflow and out of bound array access. However, we can only actually + provide well formedness constraints of expressions in the signature of + functions; what to do about abstract const expressions appearing in function + bodies in regards to well formedness is currently unclear & is delayed to + implementation. +- **Ordering and default parameters:** Do all const parameters come last, or + can they be mixed with types? Do all parameters with defaults have to come + after parameters without defaults? We delay this decision to implementation + of the grammar. [1445]: https://github.com/rust-lang/rfcs/blob/master/text/1445-restrict-constants-in-patterns.md From aaf19822286d1d42e4ac818748d7def3ff62631e Mon Sep 17 00:00:00 2001 From: Without Boats Date: Fri, 1 Sep 2017 11:14:00 -0700 Subject: [PATCH 09/12] Fix. --- text/0000-const-generics.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-const-generics.md b/text/0000-const-generics.md index fcb6064026e..e37ee950730 100644 --- a/text/0000-const-generics.md +++ b/text/0000-const-generics.md @@ -235,7 +235,7 @@ But this does, because it appears only once: type Foo = [i32; N + 1]; fn foo() -> Foo { - let x: Foo = [i32; N + 1]; + let x: Foo = Default::default(); x } ``` From dafc54e1c664a4949d38295c3b2d896c332c4d3d Mon Sep 17 00:00:00 2001 From: boats Date: Thu, 14 Sep 2017 23:09:10 +0000 Subject: [PATCH 10/12] Update RFC text --- text/0000-const-generics.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/text/0000-const-generics.md b/text/0000-const-generics.md index e37ee950730..85f3da55d3c 100644 --- a/text/0000-const-generics.md +++ b/text/0000-const-generics.md @@ -12,7 +12,7 @@ allow users to write impls which are abstract over all array types. # Motivation [motivation]: #motivation -Rust currently has one type which is parametric over constants: the built-in +Rust currently has one type which is parametric over constants: the built-inf array type `[T; LEN]`. However, because const generics are not a first class feature, users cannot define their own types which are generic over constant values, and cannot implement traits for all arrays. @@ -66,9 +66,9 @@ the length parameter of a static array. associated type. Unlike a const parameter, an associated const is *determined* by a type. -* __Const variable:__ Either a const parameter or a trait, contrast with -concrete const; a const which is undetermined in this context (prior to -monomorphization). +* __Const variable:__ Either a const parameter or an associated const, +contrast with concrete const; a const which is undetermined in this context +(prior to monomorphization). * __Concrete const:__ In contrast to a const variable, a const which has a known and singular value in this context. @@ -93,8 +93,7 @@ well all idents - e.g. `3`, `"Hello, world"`, `foo_bar`. In any sequence of type parameter declarations (such as in the definition of a type or on the `impl` header of an impl block) const parameters can also be -declared. Const parameters always come after type parameters, and their -declarations take the form `const $ident: $ty`, as in: +declared. Const parameters declarations take the form `const $ident: $ty`: ```rust struct RectangularArray { From bfd1df6071a3db7bd63909269de062b0e230a7e0 Mon Sep 17 00:00:00 2001 From: boats Date: Thu, 14 Sep 2017 23:18:21 +0000 Subject: [PATCH 11/12] RFC 2000 is const generics! :tada: --- text/0000-const-generics.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text/0000-const-generics.md b/text/0000-const-generics.md index 85f3da55d3c..e08462bbac1 100644 --- a/text/0000-const-generics.md +++ b/text/0000-const-generics.md @@ -1,7 +1,7 @@ - Feature Name: const_generics - Start Date: 2017-05-01 -- RFC PR: (leave this empty) -- Rust Issue: (leave this empty) +- RFC PR: https://github.com/rust-lang/rfcs/pull/2000 +- Rust Issue: https://github.com/rust-lang/rust/issues/44580 # Summary [summary]: #summary From 40ce721ae7315ba83c346bb9ba448885d6a4f03e Mon Sep 17 00:00:00 2001 From: boats Date: Thu, 14 Sep 2017 23:18:38 +0000 Subject: [PATCH 12/12] Rename file --- text/{0000-const-generics.md => 2000-const-generics.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename text/{0000-const-generics.md => 2000-const-generics.md} (100%) diff --git a/text/0000-const-generics.md b/text/2000-const-generics.md similarity index 100% rename from text/0000-const-generics.md rename to text/2000-const-generics.md