Skip to content
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

Merged
merged 12 commits into from Sep 14, 2017
Merged

Const generics #2000

merged 12 commits into from Sep 14, 2017

Conversation

withoutboats
Copy link
Contributor

@withoutboats withoutboats commented May 11, 2017

This is a new const generics RFC which addresses the issues around equality between const variables that were raised in this internals thread.

Rendered

[updated to link to final rendered version]

@withoutboats withoutboats self-assigned this May 11, 2017
@withoutboats withoutboats added the T-lang Relevant to the language team, which will review and decide on the RFC. label May 11, 2017
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
Copy link
Member

Choose a reason for hiding this comment

The 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.
What I have had in mind is "semantic identity", i.e. what you'd expect from nominal types, where the same node, when used multiple times, unifies with itself.

However, there is another subtlety here: consider unifying two copies of {X / 2}, each with a different inference variable for X. As far as inference is concerned, those variables don't have to be equal. After all, each loses one bit.

cc @nikomatsakis who I believe brought up the same problem with associated types recently.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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 {N + 1} should unify with itself in the same way that T::Assoc does.

Copy link
Contributor

Choose a reason for hiding this comment

The 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 X altogether for now. We don't need to add an algebra solver into the compiler, and imo X * 2 and X << 1 and X + X should all be equivalent if we allow this.

Copy link
Member

Choose a reason for hiding this comment

The 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.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@clarcharr There are certainly multiple levels of equivalence we could use.
The worst part IMO is giving more specific results from unification that can be really known.

I do want to eventually treat e.g. {N + 2} and {N + (1 + 1)} as identical, less so have any rules specific to operators or functions, but those future extensions are a bit oit of scope here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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!).

Copy link
Contributor

@gnzlbg gnzlbg Jun 16, 2017

Choose a reason for hiding this comment

The 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.

Copy link
Member

Choose a reason for hiding this comment

The 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 {N + 1} (i.e. two different nodes in the AST that both are the subtree {N + 1}) would be considered equal to each other and thus [usize; N + 1] would unify with the type of [0_usize; N + 1].

But @eddyb seems to say that contradicts what he wants to see.

Copy link
Member

Choose a reason for hiding this comment

The 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...)

Copy link
Member

Choose a reason for hiding this comment

The 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.

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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: diverge?

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 (`<T, const N: T>` is not
valid)>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: is there an extra > here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, there's just no type name with it, this should be Foo<T, const N: T>.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh I saw the wrong >. Yes!

@withoutboats
Copy link
Contributor Author

nits:

  1. I didn't actually talk about the compilation model in this RFC, should probably specify that these params are monomorphized.
  2. Consts as inputs to traits? Unclear to me what the compilation is for that. Monomorphize the traits into distinct traits?

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.
Copy link
Contributor

Choose a reason for hiding this comment

The 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:

fn foo<const X: usize>() {
  const STACK_CAP: usize = X * 2;
  let stack1 = ArrayVec<u32; STACK_CAP>::new();
  let stack2 = ArrayVec<u32; STACK_CAP>::new();
  // ...
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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

Copy link
Contributor

Choose a reason for hiding this comment

The 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).

Copy link
Member

@cramertj cramertj May 12, 2017

Choose a reason for hiding this comment

The 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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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.

Copy link
Member

@cramertj cramertj May 12, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there's no way to make it work

To clarify, you mean that there's no way to do this in the language right now, correct?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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).

Copy link
Contributor

Choose a reason for hiding this comment

The 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.

### 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"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo: "which have" has an extra space in the middle

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo: diverse -> diverge

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 (`<T, const N: T>` is not
valid)>
Copy link
Contributor

Choose a reason for hiding this comment

The 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?)


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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo: an -> a

#### 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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: commutativity


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
Copy link
Member

@cramertj cramertj May 12, 2017

Choose a reason for hiding this comment

The 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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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?

Copy link
Member

@cramertj cramertj May 12, 2017

Choose a reason for hiding this comment

The 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>`

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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).

Copy link
Member

Choose a reason for hiding this comment

The 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.

Copy link
Contributor

@gnzlbg gnzlbg Jun 16, 2017

Choose a reason for hiding this comment

The 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.

Copy link
Member

Choose a reason for hiding this comment

The 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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added an unresolved question about it.

@eddyb
Copy link
Member

eddyb commented May 12, 2017

Consts as inputs to traits? Unclear to me what the compilation is for that. Monomorphize the traits into distinct traits?

What does it mean to "monomorphize" a trait? If const parameters don't match the behavior of type parameters in some context then something has gone wrong with the semantics.

@withoutboats
Copy link
Contributor Author

withoutboats commented May 12, 2017

I'm not sure what I was thinking, its the same as multiparameter traits - we just need to create a new instance during trans for every product of types and consts. That is <T as Foo<0>>::foo generates a different from from <T as Foo<1>>::foo, but this is no different from <T as Foo<Type0>>::foo and <T as Foo<Type1>>::foo.

## When a const variable can be used

A const variable can be used as a const in any of these contexts:

Choose a reason for hiding this comment

The 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 N > 0?

Copy link
Member

Choose a reason for hiding this comment

The 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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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 const M: usze = 1 + usize::MAX;?

Copy link
Member

Choose a reason for hiding this comment

The 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:

error[E0080]: constant evaluation error
 --> <anon>:4:22
  |
4 |     const M: usize = 1 + ::std::usize::MAX;
  |                      ^^^^^^^^^^^^^^^^^^^^^ attempt to add with overflow

error: internal compiler error: /checkout/src/librustc_trans/mir/constant.rs:377: _1 not initialized
 --> <anon>:5:20
  |
5 |     println!("{}", M);
  |                    ^

note: the compiler unexpectedly panicked. this is a bug.

note: we would appreciate a bug report: https://github.com/rust-lang/rust/blob/master/CONTRIBUTING.md#bug-reports

thread 'rustc' panicked at 'Box<Any>', /checkout/src/librustc_errors/lib.rs:376
note: Run with `RUST_BACKTRACE=1` for a backtrace.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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.)

Copy link
Member

Choose a reason for hiding this comment

The 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?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when are abstract const expressions evaluated

Associated type projections are the analogy here, so: whenever <T as Trait>::Assoc would retry normalizing itself - failure due to dependence on type/const parameters simply results in the projection (abstract expression for constants) not being replaced.

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
Copy link
Member

Choose a reason for hiding this comment

The 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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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

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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So arrays are introduced as magic at first?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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.

Copy link
Contributor

@gnzlbg gnzlbg Jun 16, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So arrays are introduced as magic at first?

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.

const X: usize = 7;

let x: RectangularArray<i32, 2, 4>;
let y: RectangularArray<i32, X, {2 * 2}>;
Copy link
Contributor

Choose a reason for hiding this comment

The 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 type X for backward compatibility.

(I'm personally mildly against supporting this convenience in the initial implementation, until some experience is gained about how bad RectangularArray<i32, {X}, 0> turns out to be in practice.)

Copy link
Member

Choose a reason for hiding this comment

The 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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two things:

  1. We could consider disambiguating on the basis of the kinds of the params before falling back to assuming its a type, this is discussed in another comment thread. (Not saying we should, I'm uncertain; there are definitely cons to doing this).
  2. Allowing identity expressions is not intended as a convenience per se but to distinguish them visually from the kinds of const expressions we have to treat as projections. Its primarily pedagogical to help users understand when they can expect certain unification results and when they can't.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@eddyb

I believe we can look at the definition to know what to expect from a parameter position.

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 type X will ever cause problems in practice.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(I don't think just disambiguating in favor of type X will ever cause problems in practice.)

True!

Copy link
Contributor

Choose a reason for hiding this comment

The 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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Am I remembering correctly that struct Foo; puts Foo in both namespaces?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Am I remembering correctly that struct Foo; puts Foo in both namespaces?

That't true. Also struct Foo(u8, u8);.
So, the namespace separation is used all the time and cannot be deprecated, this is misunderstanding from the previous commenters.

Copy link
Contributor

Choose a reason for hiding this comment

The 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.

@mark-i-m
Copy link
Member

Also, where does MIRI fit into this? Is it just that when MIRI comes around consts will suddenly gain a lot of functionality?

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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this include reference types (&T/&mut T)?
They are supported in patterns and use semantic equality (reference targets are compared, not addresses themselves), despite being bitwise comparable ("structural match") as well.

Copy link
Contributor

Choose a reason for hiding this comment

The 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.
http://coliru.stacked-crooked.com/a/3301c82ba77a2f32

Copy link
Member

Choose a reason for hiding this comment

The 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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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.

@withoutboats
Copy link
Contributor Author

Also, where does MIRI fit into this? Is it just that when MIRI comes around consts will suddenly gain a lot of functionality?

Yes, MIRI is orthogonal. The RFC has a comment in it which says that for the sake of this RFC we just assume integer arithmetic works; we're drawing a distinction between the range of expressions that can be evaluated at compile time (MIRI and const fn) and making types depend on constants (const generics).

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
Copy link
Member

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.

@est31
Copy link
Member

est31 commented May 13, 2017

👍 🎉 Big support for this RFC and that it tries to not solve every issue related to const generics, but instead goes the slim but faster path, by avoiding to do anything about orthogonal issues.

I too would like to see many of the proposed extensions happen (like unifying {1+N} with {N+1}), but if they were added with this RFC it would increase chance for this RFC to be merged at a later time, and the RFC to hang in unstable limbo for longer, because of some little edge case or because it would require MIRI. I rather have the main functionality merged now, and stabilized fast, and the orthogonal issues looked at separately. Therefore, I'm very glad of the direction this RFC is going!

❤️ Many thanks @withoutboats for championing this RFC!

@clarfonthey
Copy link
Contributor

Question: does N - 1 unify with usize::MAX?

@mark-i-m
Copy link
Member

@clarcharr where do you see that?

mqudsi added a commit to mqudsi/rusqlite that referenced this pull request Sep 5, 2018
rust nightly has i128/u128 as 16-byte integer types, expected to land
into stable at some point in the (near?) future. SQLite's maximum
variable-length integer size is 8-bytes, so this needs to be serialized
into a blob for storage in the database.

Obviously endianness is a concern here; I've opted to use the platform's
native endianness rather than a hard-coded BE/LE choice. This is
contracted out to i128's `from_bytes`/`to_bytes`, which will take care
of the platform-specific endianness for us.

The usage of `unsafe` in the call to i128::from_bytes is to avoid a
potentially expensive extra allocation and looping over the contents of
the vector to copy them to a `[u8; 16]` intermediate. Once [RFC #2000](rust-lang/rfcs#2000) lands in nightly (tracked in [#44580](rust-lang/rust#44580)), [const generics](https://internals.rust-lang.org/t/lang-team-minutes-const-generics/5090) should make it possible to convert the `Vec<u8>` to a fixed-length `[u8; 16]` array in a nicer fashion.
@Centril Centril mentioned this pull request Sep 18, 2018
@Centril Centril mentioned this pull request Nov 10, 2018
@Centril Centril added A-syntax Syntax related proposals & ideas A-typesystem Type system related proposals & ideas A-expressions Term language related proposals & ideas A-const-generics Const generics related proposals. A-const-eval Proposals relating to compile time evaluation (CTFE). A-dependent-types Proposals relating to dependent types. labels Nov 23, 2018
@RustyYato RustyYato mentioned this pull request Jun 8, 2019
bors added a commit to rust-lang-ci/rust that referenced this pull request Dec 27, 2020
…ination, r=varkor

stabilize `#![feature(min_const_generics)]` in 1.51

*A new Kind*
*A Sort long Prophesized*
*Once Fragile, now Eternal*

blocked on rust-lang#79073.

# Stabilization report

This is the stabilization report for `#![feature(min_const_generics)]` (tracking issue rust-lang#74878), a subset of `#![feature(const_generics)]` (tracking issue rust-lang#44580), based on rust-lang/rfcs#2000.

The [version target](https://forge.rust-lang.org/#current-release-versions) is ~~1.50 (2020-12-31 => beta, 2021-02-11 => stable)~~ 1.51 (2021-02-111 => beta, 2021-03-25 => stable).

This report is a collaborative effort of `@varkor,` `@shepmaster` and `@lcnr.`

## Summary

It is currently possible to parameterize functions, type aliases, types, traits and implementations by types and lifetimes.
With `#![feature(min_const_generics)]`, it becomes possible, in addition, to parameterize these by constants.

This is done using the syntax `const IDENT: Type` in the parameter listing. Unlike full const generics, `min_const_generics` is limited to parameterization by integers, and constants of type `char` or `bool`.

We already use `#![feature(min_const_generics)]` on stable to implement many common traits for arrays. See [the documentation](https://doc.rust-lang.org/nightly/std/primitive.array.html) for specific examples.

Generic const arguments, for now, are not permitted to involve computations depending on generic parameters. This means that const parameters may only be instantiated using either:

1. const expressions that do not depend on any generic parameters, e.g. `{ foo() + 1 }`, where `foo` is a `const fn`
1. standalone const parameters, e.g. `{N}`

### Example

```rust
#![feature(min_const_generics)]

trait Foo<const N: usize> {
    fn method<const M: usize>(&mut self, arr: [[u8; M]; N]);
}

struct Bar<T, const N: usize> {
    inner: [T; N],
}

impl<const N: usize> Foo<N> for Bar<u8, N> {
    fn method<const M: usize>(&mut self, arr: [[u8; M]; N]) {
        for (elem, s) in self.inner.iter_mut().zip(arr.iter()) {
            for &x in s {
                *elem &= x;
            }
        }
    }
}

fn function<const N: u16>() -> u16 {
    // Const parameters can be used freely inside of functions.
    (N + 1) / 2 * N
}

fn main() {
    let mut bar = Bar { inner: [0xff; 3] };
    // This infers the value of `M` from the type of the function argument.
    bar.method([[0b11_00, 0b01_00], [0b00_11, 0b00_01], [0b11_00, 0b00_11]]);
    assert_eq!(bar.inner, [0b01_00, 0b00_01, 0b00_00]);

    // You can also explicitly specify the value of `N`.
    assert_eq!(function::<17>(), 153);
}
```

## Motivation

Rust has the built-in array type, which is parametric over a constant. Without const generics, this type can be quite cumbersome to use as it is not possible to generically implement a trait for arrays of different lengths. For example, this meant that, for a long time, the standard library only contained trait implementations for arrays up to a length of 32. This restriction has since been lifted through the use of const generics.

Const parameters allow users to naturally specify variants of a generic type which are more naturally parameterized by values, rather than by types. For example, using const generics, many of the uses of the crate [typenum](https://crates.io/crates/typenum) may now be replaced with const parameters, improving compilation time as well as code readability and diagnostics.

The subset described by `min_const_generics` is self-contained, but extensive enough to help with the most frequent issues: implementing traits for arrays and using arbitrarily-sized arrays inside of other types. Furthermore, it extends naturally to full `const_generics` once the remaining design and implementation questions have been resolved.

## In-depth feature description

### Declaring const parameters

*Const parameters* are allowed in all places where types and lifetimes are supported. They use the syntax `const IDENT: Type`. Currently, const parameters must be declared after lifetime and type parameters. Their scope is equal to the scope of other generic parameters. They live in the value namespace.

`Type` must be one of `u8`, `u16`, `u32`, `u64`, `u128`, `usize`, `i8`, `i16`, `i32`, `i64`, `i128`, `isize`, `char` and `bool`. This restriction is implemented in two places:

1. during name resolution, where we forbid generic parameters
1. during well-formedness checking, where we only allow the types listed above

The updated syntax of parameter listings is:

```
GenericParams:
    (OuterAttr* LifetimeParam),* (OuterAttr* TypeParam),* (OuterAttr* ConstParam),*

OuterAttr: '#[' ... ']'
LifetimeParam: ...
TypeParam: ...
ConstParam: 'const' IDENT ':' Type
```

Unlike type and lifetime parameters, const parameters of types can be used without being mentioned inside of a parameterized type because const parameters do not have issues concerning variance. This means that the following types are allowed:

```rust
struct Foo<const N: usize>;
enum Bar<const M: usize> { A, B }
```

### Const arguments

Const parameters are instantiated using *const arguments*. Any concrete const expression or const parameter as a standalone argument can be used. When applying an expression as const parameter, most expressions must be contained within a block, with two exceptions:

1. literals and single-segment path expressions
1. array lengths

This syntactic restriction is necessary to avoid ambiguity, or requiring infinite lookahead when parsing an expression as a generic argument.

In the cases where a generic argument could be resolved as either a type or const argument, we always interpret it as a type. This causes the following test to fail:

```rust
type N = u32;
struct Foo<const N: usize>;
fn foo<const N: usize>() -> Foo<N> { todo!() } // ERR
```

To circumvent this, the user may wrap the const parameter with braces, at which point it is unambiguously accepted.

```rust
type N = u32;
struct Foo<const N: usize>;
fn bar<const N: usize>() -> Foo<{ N }> { todo!() } // ok
```

Operations depending on generic parameters are **not** allowed, which is enforced during well-formedness checking. Allowing generic unevaluated constants would require a way to check if they would always evaluate successfully to prevent errors that are not caught at declaration time. This ability forms part of `#![feature(const_evaluatable_checked)]`, which is not yet being stabilised.

Since we are not yet stabilizing `#![feature(lazy_normalization_consts)]`, we must not supply the parent generics to anonymous constants except for repeat expressions. Doing so can cause cycle errors for arrays used in `where`-bounds. Not supplying the parent generics can however lead to ICEs occurring before well-formedness checking when trying to use a generic parameter. See rust-lang#56445 for details.

Since we expect cases like this to occur more frequently once `min_const_generics` is stabilized, we have chosen to forbid generic parameters in anonymous constants during name resolution. While this changes the ICE in the situation above to an ordinary error, this is theoretically a breaking change, as early-bound lifetimes were previously permitted in repeat expressions but now are disallowed, causing the following snippet to break:

```rust
fn late_bound<'a>() {
    let _ = [0; {
        let _: &'a (); // ICE ==> ERR
        3
    }];
}

fn early_bound<'a>() where &'a (): Sized {
    let _ = [0; {
        let _: &'a (); // ok ==> ERR
        3
    }];
}
```

### Using const parameters

Const parameters can be used almost everywhere ordinary constants are allowed, except that they may not be used in the construction of consts, statics, functions, or types inside a function body and are subject to the generic argument restrictions mentioned above.

Expressions containing const parameters are eligible for promotion:

```rust
fn test<const N: usize>() -> &'static usize {
    &(3 + N)
}
```

### Symbol mangling

See the [Rust symbol name mangling RFC](https://rust-lang.github.io/rfcs/2603-rust-symbol-name-mangling-v0.html) for an overview. Generic const parameters take the form `K[type][value]` when the value is known, or `Kp` where the value is not known, where:
- `[type]` is any integral type, `bool`, or `char`.
- `[value]` is the unsigned hex value for integers, preceded by `n` when negative; is `0` or `1` for `bool`; is the hex value for `char`.

### Exhaustiveness checking

We do not check the exhaustiveness of impls, meaning that the following example does **not** compile:

```rust
struct Foo<const B: bool>;
trait Bar {}
impl Bar for Foo<true> {}
impl Bar for Foo<false> {}

fn needs_bar(_: impl Bar) {}
fn generic<const B: bool>() {
    let v = Foo::<B>;
    needs_bar(v);
}
```

### Type inference

The value of const parameters can be inferred during typeck. One interesting case is the length of generic arrays, which can also be inferred from patterns (implemented in rust-lang#70562). Practical usage of this can be seen in rust-lang#76825.

### Equality of constants

`#![feature(min_const_generics)]` only permits generic parameters to be used as standalone generic arguments. We compare two parameters to be equal if they are literally the same generic parameter.

### Associated constants

Associated constants can use const parameters without restriction, see rust-lang#79135 (comment) for more details.

## Future work

As this is a limited subset of rust-lang/rfcs#2000, there are quite a few extensions we will be looking into next.

### Lazy normalization of constants

Stabilizing `#![feature(lazy_normalization_consts)]` (tracking issue rust-lang#72219) will remove some special cases that are currently necessary for `min_const_generics`, and unblocks operations on const parameters.

### Relaxing ordering requirements between const and type parameters

We currently restrict the order of generic parameters so that types must come before consts. We could relax this, as is currently done with `const_generics`. Without this it is not possible to use both type defaults and const parameters at the same time.

Unrestricting the order will require us to improve some diagnostics that expect there to be a strict order between type and const parameters.

### Allowing more parameter types

We would like to support const parameters of more types, especially`&str` and user-defined types. Both are blocked on [valtrees]. There are also open questions regarding the design of `structural_match` concerning the latter. Supporting generic const parameter types such as `struct Foo<T, const N: T>` will be a lot harder and is unlikely to be implemented in the near future.

### Default values of const parameters

We do not yet support default values for const parameters. There is work in progress to enable this on nightly (see rust-lang#75384).

### Generic const operations

With `#![feature(min_const_generics)]`, only concrete const expressions and parameters as standalone arguments are allowed in types and repeat expressions. However, supporting generic const operations, such as `N + 1` or `std::mem::size_of::<T>()` is highly desirable. This feature is in early development under `#![feature(const_evaluatable_checked)]`.

## Implementation history

Many people have contributed to the design and implementation of const generics over the last three years. See rust-lang#44580 (comment) for a summary. Once again thank you to everybody who helped out here!

[valtrees]: rust-lang#72396

---

r? `@varkor`
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-const-eval Proposals relating to compile time evaluation (CTFE). A-const-generics Const generics related proposals. A-dependent-types Proposals relating to dependent types. A-expressions Term language related proposals & ideas A-syntax Syntax related proposals & ideas A-typesystem Type system related proposals & ideas final-comment-period Will be merged/postponed/closed in ~10 calendar days unless new substational objections are raised. T-lang Relevant to the language team, which will review and decide on the RFC.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet