-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Const generics #2000
Const generics #2000
Changes from 3 commits
ac64358
90cbc6d
2ec7001
2119312
5cd2118
122fab1
5a383d5
5afdf55
aaf1982
dafc54e
bfd1df6
40ce721
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: how does this interact with default type parameters? Can a struct have default type parameters and const parameters? Edit: I ask because default type parameters are required to be listed at the end of the type parameter list. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't feel like I've ever properly understood this concern. Can't we determine from the kind of the node you put there and (usually) how many arguments you've supplied whether it is intended to be a const or a type? The only case that seems ambiguous to me is something like this: struct Foo<T = i32, const N: usize = 0>([T; N]);
fn foo<T, const T: usize>(_: Foo<T>) { } That is you have both const and type default parameters, and you have an ident which is a name in both type and const context (bad news in general), and you supply it once to the type. I don't particularly care what we do here since its such an edge case (probably treat it as the type parameter). Am I missing something? Why wouldn't this Just Work? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh it very well may "Just Work." I'm just wondering what the plan would be. I think this looks a little odd, for example, since it results in "skipping" a type parameter: struct Foo<A, B=i32, const N: usize>(A, [B; N]);
fn foo(x: Foo<String, 4>) {...} // The default makes this `Foo<String, i32, 4>` There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess I don't think it looks odd because we elide lifetimes all the time (which is problematic, but not in a way that applies here). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @withoutboats We can only determine whether an identifier is meant to be a type or a constant by checking what its position is declared as - you can right now have both a type and a const defined/imported with the same name in a scope and it disambiguates just fine. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is having a type and a const defined/imported with the same name in a scope useful? That is so confusing when talking about const level values that I have to ask whether it wouldn't be better to deprecate it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think we ever resolved this point. @withoutboats @eddyb Have either of you had any ideas since we discussed? I think it's necessary to have a backwards-compatible way to add default type parameters to things that already have const parameters. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I added an unresolved question about it. |
||
declarations take the form `const $ident: $ty`, as in: | ||
|
||
```rust | ||
struct RectangularArray<T, const WIDTH: usize, const HEIGHT: usize> { | ||
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<i32, 2, 4>; | ||
let y: RectangularArray<i32, X, {2 * 2}>; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Minor detail: a resolution ambiguity case is possible: type X = u8;
const X: u8 = 0;
let _: RectangularArray<i32, X, 0>; // Is `X` a type or a constant? This needs to be disambiguated in favor of (I'm personally mildly against supporting this convenience in the initial implementation, until some experience is gained about how bad There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe we can look at the definition to know what to expect from a parameter position. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Two things:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Not in general case. type X = u8;
const X: u8 = 0;
Type::method<X>; // We can't look at the definition of `method`, it's only available during type checking.
value.method::<X>(); // Same here. (I don't think just disambiguating in favor of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
True! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Having a constant and a type with the same identifier is extremely confusing, and even more so if constants can be "types". Why can't this be deprecated? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I also think it is confusing. Some projects (including the compiler at one point!) take advantage of these two namespaces to create functions with the same names as types to get "constructor syntax." I don't think this is a good idea, and I would be in favor of warning on it, but that's separate from this RFC probably. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Am I remembering correctly that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
That't true. Also There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fortunately, defaulting to the type when there's an ambiguity seems reasonable in both this case and the "constructor syntax" case. |
||
``` | ||
|
||
### 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: | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the cases that don't already make this clear, can a const expression involving a const variable also be used in these contexts? And when are they evaluated, e.g. (when) does impl<const N: usize> SomeType<N> {
const M: usize = N + usize::MAX
} error if There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd say that if it ends up in a type, it can be considered an implement WF requirement for that type, propagating outwards so if it ends up in a concrete type written/inferred, then there would be an error - but if the error comes from monomorphizing a function, it can only be a warning, as per #1229. @rust-lang/lang might disagree with me, but I think they'd agree we should specify something in this RFC. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When / why should we do something different than whatever we do when a user writes There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @withoutboats That's an ICE right now on nightly, although it does emit a const-eval error first:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unfortunate, but I'm trying to get at what needs to be specified by this RFC (trying to keep it as orthogonal as possible from the const eval system.) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It seems like there's const eval and const eval for type unification. The first I agree is orthogonal, but the second I think should be mentioned in the RFC... for example, when are abstract const expressions evaluated (it looks like monomorphization time right now)? do they use the same mechanisms as normal const eval? when are unification errors discovered by the compiler? how does this change the current unification algorithm? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Associated type projections are the analogy here, so: whenever |
||
1. As an applied const to any type which forms a part of the signature of | ||
the item in question: `fn foo<const N: usize>(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<const N: usize>([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 X: usize>() { | ||
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. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm perfectly fine with shipping with this rule, but can you elaborate on... why? It seems unfortunate that this doesn't work:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @gankro The same reason this doesn't work: fn foo<I: Iterator>(iter: I) {
fn bar(item: I::Item) { }
} It would make that internal const a kind of secret associated const of the function, rather than its own item. Obviously this could work someday (even the function example I comment here could work someday) but in the name of incrementalism it's a separate feature. Possibly we could make an exception for consts (not statics, types, or functions) since they have no representation in the compiled binary. cc @eddyb on this one There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah I was only thinking of consts. Since they're basically just named temporaries, it seems totally fine (no weird codegen implications like statics). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @withoutboats This doesn't work: fn foo<I: Iterator>(iter: I) -> fn(I::Item) {
fn bar(item: I::Item) { }
bar
}
fn bla<I: Iterator>(iter: I) {
type Bla = I;
} But this does: fn foo<I: Iterator>(iter: I) -> fn(I::Item) {
fn bar<I: Iterator>(item: I::Item) { }
bar::<I>
}
fn bla<I: Iterator>(iter: I) {
type Bla<I> = I;
} So there's already a way to work around it for functions and types. Can you think of a similar way we could make it work for consts and statics? Like @gankro, I think it makes sense for it to "just work" for consts, but I don't know about statics. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The same problem applies with type arguments in consts and statics today, this doesn't work and there's no way to make it work: fn foo<I: Iterator>() {
const NUL: Option<I::Item> = None;
} I think solving this is the same for both const params and type params, so its an orthogonal RFC from this one. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
To clarify, you mean that there's no way to do this in the language right now, correct? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes! Unlike functions you can't thread a parameter into there. (I think the solution is to make consts Just Work and say sorry about statics). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @withoutboats can you update the RFC with this information? I had exactly this same question. |
||
|
||
## 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" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. typo: "which have" has an extra space in the middle |
||
property can be used as const parameters. This would exclude floats, for | ||
example. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this include reference types ( There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. C++ supports reference const parameters and uses "structural" bitwise comparison to unify them. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "structural match" for references isn't pointer equality though, they're considered to be equivalent to a newtype for that purpose. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe we should get whatever the match semantics over, so it will compare the targets. Seems very important to be certain we unify two identical string literals even if for whatever reason they are allocated separately in rodata. |
||
|
||
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: diverge? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. typo: diverse -> diverge |
||
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To be clear, this would work for user-defined types, too, right? As long as they have structural equality? How does this work exactly? Do we just refuse to compile if they use a type that overloads equality? Or is operator overloading irrelevant? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The RFC for structural_match should answer your questions I think: https://github.com/rust-lang/rfcs/blob/master/text/1445-restrict-constants-in-patterns.md |
||
cannot be enforced for a type variable, it is not possible to introduce a const | ||
parameter which is ascribed to a type variable (`<T, const N: T>` is not | ||
valid)> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: is there an extra > here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, there's just no type name with it, this should be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. oh I saw the wrong >. Yes! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is the structural match property not intended to be exposed as a trait? (why not?) |
||
|
||
### 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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. typo: an -> a |
||
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My sole nit for this entire RFC is this: "syntactic equality" is, IMO, unactionable. However, there is another subtlety here: consider unifying two copies of cc @nikomatsakis who I believe brought up the same problem with associated types recently. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yea I wasn't sure how to phrase this correctly; what I was trying to get across was that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I personally would like to avoid the notion of generic equality beyond just There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That seems accurate, I wonder how attached @nikomatsakis is to that rule - it's a trade-off. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @clarcharr There are certainly multiple levels of equivalence we could use. I do want to eventually treat e.g. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @eddyb let's talk out of band about what the right wording for this section is. We're starting out more conservatively than I thought (which is fine with me!). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @withoutboats @eddyb I think that saying that "An expression only unifies with itself" and maybe adding @mark-i-m 's example as a clarification (maybe with some comments) would suffice to make it clear what you exactly mean by "with itself". EDIT: the RFC still needs to be updated with something like this. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @rfcbot concern expression-unifies-with-itself Was this thread of discussion ever resolved? On my latest reading (Sept 1), I came away with the impression that two occurrences of the AST But @eddyb seems to say that contradicts what he wants to see. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (or is @eddyb's sole point merely that he anticipates this being an initial implementation limitation, but not a problem with the fundamental design here... I remain confused...) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The initial implementation will consider them distinct, but we can start work on unification strategies after we have anything working at all. |
||
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: commutativity |
||
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. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So arrays are introduced as magic at first? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The exact documentation might foremention that you can define your own types with const parameters, but we should avoid bogging users down in a deep understanding of this (or any) feature. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I don't recall what the book says when arrays are introduced, does it say that user defined types can also be parametrized by types? If yes, we should add "and values". Otherwise, I don't see the need. |
||
|
||
# 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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we need to work on a shorthand for statements like this, because obviously most new features are going to add additional complexity somewhere. It's just noise. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In this case it is definitely not noise; this is probably the largest feature we've considered adding since 1.0 and only two others even come close (specialization and impl Trait). |
||
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note: not just the standard library itself has this limitation, but also libraries like serde have it.