Rust: Tracking issue for const generics (RFC 2000)

Created on 15 Sep 2017  Ā·  202Comments  Ā·  Source: rust-lang/rust

Tracking issue for rust-lang/rfcs#2000

Updates:

If you want to help out, take a look at the open const generics issues and feel free to ping @varkor, @eddyb, @yodaldevoid, @oli-obk or @lcnr for help in getting started!


Blocking stabilization:

  • [ ] Design:

    • [ ] Resolving ordering of const and type parameters, with default parameters

    • [ ] Decide what the best UX / implementation cost balance is for unifying abstract const expressions.

    • [ ] How we determine well formedness of const expressions.

  • [x] Implementation
  • [ ] Documentation

    • [ ] rustc guide


Remaining implementation issues:

  • [ ] Resolve various FIXME(const_generics) comments.
  • [ ] Resolve concerns with canonicalisation / lazy normalisation.
  • [ ] Investigate handling of const parameters in patterns.
  • [ ] Add more tests.
  • [ ] Implement defaults for const parameters (FIXME(const_generics:defaults)).
  • [ ] Fix other A-const-generics issues.
  • [ ] Audit uses of has_infer_types.
  • [x] Forbid complex expressions for const arguments involving parameters (for now), e.g. {X * 2}.
  • [ ] Audit diagnostics (e.g. https://github.com/rust-lang/rust/pull/76401#discussion_r484819320).
A-const-fn A-const-generics A-typesystem B-RFC-approved C-tracking-issue F-const_generics T-compiler T-lang requires-nightly

Most helpful comment

Here's a summary of progress so far on const generics.


Before work on const generics could properly begin, there was some refactoring that needed to be done. @jplatte took on the task with https://github.com/rust-lang/rust/pull/45930. @jplatte then started working on the main implementation of const generics, but found they didn't have enough time to continue.

@yodaldevoid and I picked up where @jplatte left off, but quickly found that making progress was stymied by the way generic parameters were handled in general. Thus began a series of changes to revamp the way generics were handled throughout the codebase: https://github.com/rust-lang/rust/pull/48149, https://github.com/rust-lang/rust/pull/48452, https://github.com/rust-lang/rust/pull/48523, https://github.com/rust-lang/rust/pull/51880.

With that done, the implementation of const generics could begin in earnest. Since then, @yodaldevoid and I have slowly but surely been incrementally adding support for const generics: https://github.com/rust-lang/rust/pull/58191, https://github.com/rust-lang/rust/pull/58503, https://github.com/rust-lang/rust/pull/58581, https://github.com/rust-lang/rust/pull/58583, https://github.com/rust-lang/rust/pull/59170, https://github.com/rust-lang/rust/pull/59355, https://github.com/rust-lang/rust/pull/59415, https://github.com/rust-lang/rust/pull/60058, https://github.com/rust-lang/rust/pull/60280, https://github.com/rust-lang/rust/pull/60284 and most recently https://github.com/rust-lang/rust/pull/59008. (These have mostly been split out from the main const generics pull request.)


What's the status now? Some const generics tests now work šŸŽ‰However, there are still those that don't, and there are a number of FIXMEs throughout the code that we still have to address. We're getting close now, though, and we're at the stage where there's some low-hanging fruit if you want to help.

  • First, we need more tests. There are only a handful of const generics tests so far, but we'd like many more. At the moment, because we're aware of a number of issues, we don't need bug reports, but we do need passing tests. If you come up with a test case that works (and doesn't look too similar to an existing test), feel free to open a pull request to add it.
  • Secondly, as mentioned, there are a number of FIXME(const_generics) scattered throughout the code. We're planning on working our way through them, but @yodaldevoid and I only have so much time, so if you think you can tackle one, go ahead (though you might like to leave a comment saying as much, so we don't duplicate efforts).

I've written an overview of some of the remaining implementation issues before const generics will be ready for proper testing, in the top post, to give a rough idea of what's left to do.

It's taken time, but we've steadily been making progress and hope to keep up the pace. It's motivating to finally see things starting to fall into place!

All 202 comments

44275 added a ConstEvaluatable predicate, and WF([T; expr]) now requires ConstEvaluatable(expr), as expr is lazily evaluated (even if it's just an integer literal). Fulfilling this predicate requires the expression to evaluate successfully, while normalization ignores the error and simply leaves the Unevaluated expression it found, untouched, which is more or less what happens with associated type projections. I expect the same system to scale to const generics.

@EpicatSupercell has expressed interest in working on this, I'll mentor them through the initial implementation. However, we can't go too far because of the limitations described in #44275.

That is, we need @nikomatsakis' lazy normalization to allow constant expressions embedded in types to observe the bounds in scope (from the function / type definition / impl / etc. item they're in), without producing cyclic dependencies half the time.

Implementation waypoints (for more direct mentoring, seek @eddyb on Gitter or eddyb on IRC):

  • Declaration / Syntax:
  • Declaration / Semantics

    • Data structures: ty::Generics - add const parameters alongside type ones

    • Conversion from HIR: generics_of - create ty::ConstParameterDef from hir::ConstParam

  • Use / Name Resolution

    • Data structures: Def - add a variant for const parameters

    • Name resolution pass: with_type_parameter_rib - support both type and const generics

  • Use / Semantics

    • ConstVal - add Param variant akin to ty::TyParam

    • Conversion from HIR: TyArray with a length that's an ExprPath that resolved to Def::ConstParam should use ConstVal::Param instead of ConstVal::Unevaluated - in a similar fashion to how Def::TyParam turns into ty::TyParam

    • subst::Kind - support &ty::Const and check as_const as well in where places where as_type and as_region are checked

  • Inference

Note that all of this should allow impl<T, const N: usize> Trait for [T; N] {...}, but not actually passing a constant expression to a type/function, e.g. ArrayVec<T, 3>.

I'd like to take a stab at this :)

@jplatte @eddyb Any news on this?

@samsartor there was a refactoring that had to be done before the main implementation work. That is now almost done (I'm waiting for feedback currently). I don't actually know how much work there is after that. Parsing const params was what I started with initially, before the refactoring. It's not done but I should be able to get that done relatively soon.

@jplatte It would be nice, if you could link the pull request. I was not able to find it.

There is no PR yet. All my work can be found here.

Nice work so far @jplatte šŸ»

There is now a PR for the groundwork (Generics refactoring): #45930

I think this is already included but it would be good to handle the C++ style folding for handling linear algebra style n dimensional arrays with varying lengths, I.E. a 4x4 [[f64;4];4]or a 4x3x5x6 dimensional array [[[[f64;6];5];3];4] and be able to properly wrap and generate specialized methods for both it AND proper trait implementations for scalars properly dimensioned vectors, etc. See https://gist.github.com/huhlig/8b21850b54a75254be4b093551f8c2cb for a rudamentary example.

I don't recall anyone proposing fold expressions for Rust before, much less as part of this RFC. But since this is Rust, is there any reason we couldn't implement fold expressions as an ordinary macro, rather than new dedicated syntax?

What are the next steps now that #45930 is merged? @jplatte

@kjaleshire The next step is to implement parsing of const generics (see @eddyb's comment further up). I've already started work on this before it became clear that the refactoring in the refactoring would be necessary. My existing work on that can be found here; it hasn't yet been rebased since the refactoring PR was merged though.

@jplatte I believe you meant to mention @kjetilkjeka.

Thanks for the update! I'm sure I'm far from the only one looking forward to this feature. Keep up the good work!

@jplatte not wanting to be impatient, but has there been any work on this? Do you need help? Should someone help out?

@est31 Sorry. Haven't had time to work on this in a while, and I'm not entirely sure when I will have the time. Maybe it's best for someone else to continue where I left off. I think the parsing part is mostly done. There are two places in the code where I wasn't sure what to do in the case of a generic param being a const param:

There also aren't any tests for the parsing of const generics yet (and for the pretty-printing code that I updated at the same time). Not sure what other information I can provide that would be needed / helpful for someone else to continue working on this, but feel free to ping me if something is unclear about my code.

@jplatte ~None in the first and nothing in the second~~ (cc @pnkfelix @nikomatsakis)

EDIT: nothing to do in either case.

@eddyb For the first linked snippet are you sure that None should be returned? I may not understand what is going on here, but it seems to me that nothing should be done. If None is returned other params that might be unsafe will be skipped.

@yodaldevoid Sorry, you're right, I didn't realize this was on Generics.

I would like to pick this up where @jplatte left off. I've been working away at this for a couple days now, going through @eddyb's mentoring instructions as I've had time just to see if I could get anywhere. I've gotten down to the "Use / Semantics" section at this point. I'll probably swing by one of the chats soon enough to ask questions.

@yodaldevoid Great to hear. I don't know about the Gitter, but you'll definitely get plenty of guidance on #rustc and/or #rust-internals on IRC!

@yodaldevoid: would you be up for some collaboration? I've also been doing some investigation into this issue (šŸ˜…) ā€” perhaps we could fill in the gaps in each other's work. (You can see what I've done so far here.) Maybe we could chat on IRC (#rustc or #rust-internals) about it?

@varkor It looks like you are further ahead than I got. I would certainly be willing to collaborate. I'll try to grab you on IRC at some point and in the mean time see if I've gotten anything done that you haven't already.

Is there any progress in this?
I'm writing code for embedded, and I really need const generics.

@qwerty19106
Progress is being made on this, though slowly. @varkor and I have been working on this off and on as we had time. I made some progress this week and we are seeing the light at the end of the tunnel for basic usage.

Beyond just implementing const generics, we (varkor) have done some cleanup to make this all possible (see #48149 and #48523). I believe the current plan is to wait for those two pull requests to go through before pulling in const generics, but varkor can speak more to that.

I really understand your need for const generics for embedded work. I started on this because I too really want const generics so I can clean up and make type safe large swathes of embedded code.

TL;DR: Progress is going, but this is complex. I feel you on the embedded front.

For the "documentation" checkbox, it would be great to get something in the rustc guide.

Thanks @yodaldevoid for your reply. I will look forward for the end of your work.

Update for those curious (since I was also curious). Re: the two PRs mentioned above: #48523 has merged and #48149 is steadily making progress.

@mark-i-m Good stuff! Nice work by @varkor. When's ETA roughly, do you know? :-)

Cheers, @flip111.

Looks like the second big refactoring PR #48149 got merged :)

/cc me

Next @varkor PR #51880

Just wanted to give a quick update, as I know some people have been asking about the progress with const generics.

To give some context, in March , @yodaldevoid and I had an initial implementation that was almost working (the bulk of the implementation seemed to be done, and we were just cleaning up some remaining crashes). However, the branch we were working on was pre-miri. When miri was merged (and in a number of subsequent PRs), the code for dealing with constant evaluation changed significantly, meaning a lot of what we had done became outdated.

On top of that, it was decided that the generic parameters code needed to be cleaned up in general before tacking const generics on, both to improve readability, but also to make mistakes where we forgot to deal with const generics in a certain case harder to make. This was done in https://github.com/rust-lang/rust/pull/48523, https://github.com/rust-lang/rust/pull/48149 and will be completed in https://github.com/rust-lang/rust/pull/51880. These were a little more involved than I initially expected, and they've taken a little longer to push through than estimated.

In the meantime, @yodaldevoid and I have been working on making our original implementation compatible with all the subsequent changes in rustc. It's taken a while, but we're getting there (though there's the perennial problem of never having as much time as you expected). I hope we'll have some good news soon on that front. (Meanwhile, https://github.com/rust-lang-nursery/chalk has been making good progress, which should address some of the difficulties @eddyb originally described.)


Some people have asked how they can help: I think at this stage, it's going to be easier for us to try to finish off the initial implementation, and then see which parts need attention. Once that's ready, we're going to need a lot of tests, utilising const generics in different places (including invalid ones, for error messages), so that's definitely somewhere we could do with a lot of help! We'll let you know when that happens!

Sorry if it's not the appropriate place for that, but I have a suggestion regarding equality of abstract const expressions. In general it directly reduces to full dependent typing and undecidable territory. However, in rust everything is eventually instantiated with concrete values/types, so we can assert some equalities and postpone their checking to monomorphic instances.

What I mean is that a relatively simple way of checking the equality of abstract const expressions is the following:

  • automatically deal with syntactic equality (ie. N+1 == N+1 should work out of the box)
  • allow the user, at definition time, to add equations such as N+M == M+N (maybe in the where clause?). These equations can be used by the equality check (using some form of congruence closure). A definition that doesn't type check using these provided equations is rejected.
  • at a monomorphic expansion point, all the equations can be checked by actually computing the const exprs, which are not abstract anymore. Here there's a design choice: if an equation that reduces to false, either there could be a compilation error ("equations are axioms") or the trait cannot be instantiated ("equations are constraints")
  • at a parametrized expansion point, these equations are carried over: if you have a function f parametrized by N where N+0==N, this equation must not be lost in a caller g since it'll need to be checked at each monomorphic place where g is called.

The pros of this method is that there isn't a need for a theorem prover, SMT solver, or arithmetic rewriter in rustc itself. "Only" a syntactic equality check of types, modulo aliases and modulo a set of equations provided by the user.
The cons is that it's more manual for the user since seemingly obvious equalities like M+N=N+M have to be added explicitly.

Example:

/// this works directly because of syntactic checks
fn add_end<T:Copy, const N: usize>(a: &[T;N], b: T) -> [T;N+1] {
  let x : [T;N+1] = [b;N+1];
  x[0..N] = a;
  x
}

/// this should work already
fn append<T:Copy, const M:usize, const N:usize>(a: &[T;M], b: &[T;N])
  -> [T;{M+N}]
{ ā€¦ }

/// Here the equation M+M==N must be carried over or checked whenever this function is used
fn sum_pairwise_append<const M: usize, const N: usize>(a: &[i32, M],b: &[i32;N])-> [T;N]
  where M+M == N {
  let mut res : [i32; N] = append(a,a);
  for i in 0 .. N { res[i] += b[i] };
  res
} 


fn main() {
  let a: [i32; 2] = [1;2];
  let b: [i32; 4] = [2;4];
  let _ = sum_pairwise_append(a, b);  // need to check 2+2=4 
}

Equations as axioms

This means that e1 == e2 should always be true, so it's not really a where constraint but more like a assert_eq!(e1,e2) that can be used by the type checker. Here the implementor makes a promise that this is always true, and exposes her users to compilation errors if they provide parameter that refute the equation.

Equations as constraints

Here a clause where e1 == e2 is a constraint that must be satisfied for the successful use of this parametrized trait/function. That means that e1 == e2 doesn't have to always hold, which may be interesting for equations only true over a domain, such as (x*y) / z == (y*x) / z which would fail to instantiate for z==0.

@c-cube We already have a system for "implicit where clauses", at least for functions, derived from "WF (well-formedness) rules", i.e. if you define a fn foo<'a, 'b>(x: &'a &'b i32) {} then it requires callers to satisfy 'b: 'a (as a result of needing WF(&'a &'b i32) -> &'b i32: 'a -> 'b: 'a).

We can reuse that system (in fact this is already implemented) to push constraints given by the signature (of the form "this constant expression evaluates successfully") up to the callers, whereas anything used only inside the body would still need where clauses.

Most of everything else you're describing seems close to what's planned (including a form of "syntactic equality"), but we don't want "monomorphization-time" checks, we can instead have ways (either implicit or through where clauses) to "propagate constraints to instantiators", and then we produce errors where constraints are "still too generic" (so unknown if they hold), or otherwise if we can evaluate them and they don't happen to hold.

Most of everything else you're describing seems close to what's planned

@eddyb do you know any reference posts that discusses these plans?

@flip111 Probably some of the RFCs. Maybe @withoutboats knows better.

#51880 is done :tada: :tada:

@withoutboats @oli-obk What do you think about blocking proper unification of expressions like N + 1 (from e.g. [T; N + 1]) on getting some basic form of symbolic evaluation in miri?

I think we'd already have a mechanism to encode it, when @varkor adds ConstValue::{Infer,Param}, we can use that to track "(shallowly) valid but unknown" (symbolic) values, and then also include value (mainly integers) operations and calls on top of that.

Any non-assert control-flow decisions would still require known values, but assert itself could perhaps be reified as AssertThen(condition, assert message, success value).
(Both the condition and the success value could be symbolic!)

Being able to export the symbolic values into ty::Const means we can implement some of the unification outside miri, treating (most?) operations as opaque.

We have to be careful not to assume anything of inference variables, but for e.g. N + 1 I think we can support unifying two Add(Param(N), Bits(1)) together.

@varkor A test-case I think should work with just lazy normalization, no unification:

/*const*/ fn append<const N: usize, T>(xs: [T; N - 1], x: T) -> [T; N] {
    let new = MaybeUninit::<[T; N]>::uninitialized();
    unsafe {
        let p = new.as_mut_ptr() as *mut T;
        (p as *mut _).write(xs);
        p.add(N - 1).write(x);
    }
    new.into_inner()
}

That would only work because:

  • N - 1 shows up at the type level exactly once

    • everything can refer to that expression, opaquely

    • (potential workaround: type ArrayWithoutLast<T, const N: usize> = [T; N - 1];)

  • it's in an argument position within the signature

    • (can't remember if return position would also work. @nikomatsakis?)

    • callers get to prove it's WF, by providing concrete values for N

    • "bubbling up" the WF requirement needs unification / ArrayWithoutLast

    • other uses would need "embedding in a where clause" e.g. [T; N - 1]: Sized



      • can even abuse where [T; N - 1]: (does that get ignored today? ouch!)


      • again bypass unification with where ArrayWithoutLast<T, N>: ...



So overall, we can probably rely on WF rules to force "validation" of const expressions onto the users, but we'd still want unification for other things (including not needing hacks like ArrayWithoutLast).

53645

Small question which originates from discussion of Units of Measure. What should we do if type generic over constant does not directly depend on it? For example:

struct Foo<T, const N: usize> {
    a: T,
    // Will such array be "the way" to handle this problem?
    // Or do we want some kind of `std:marker::PhantomConstData`? (which can be a simple
    // type alias to the same array)
    // Or maybe make `PhantomData` to accept constants?
    _phantom: [(), N],
}

_phantom: [(), N],

I assume you meant [(); N], but this will only work for usize.

I think it would make sense to have a special marker::PhantomConst allowing any const type.

Hmmā€¦ Re-reading the RFC with the latest comments in mind, I'm wondering: would something like this be allowed?

struct Foo<T, const V: T> {
    _phantom: PhantomConst<T, V>,
}

I can't see it being banned anywhere, but would have thought it'd deserve at least an example.

@Ekleog: as far as I'm aware, const parameter types may not depend on type parameters initially (though it would definitely make sense for an extension). (The implementation is trickier if we allow this, so it makes sense to start with the simplest version.)

How is the progress on this? Do we know an approximate time when this should hit nightly?

@Zauberklavier As soon as this pull request is done. To quote from it:

There's a long way to go

@newpavlov I assumed constant parameters would be allowed without being used in fields.
The reason type parameters must be used is because of variance, but constants don't have this issue.

@eddyb that's what I recall from the RFC discussion also.

@varkor So this means PhantomConst proposed by @cuviper cannot exist in the current state of this RFCā€¦ right? though latest comments appear to point that there is no need for PhantomConst anyway.

Do const parameters impact the variance of type parameters they involve?

Currently I believe const generics can't have type parameters in their type.

I am not clear on what the first working version of this will be able to do.

For example whether the code in this comment would compile:
https://github.com/rust-lang/rfcs/pull/2581#discussion_r230043717

If that code compiled,it would be a way to enforce constraints for constants before getting a syntax for it.

@rodrimati1992 You can probably just do (): IsTrue<{N < 128}> without a separate type.
I guess you want to reuse the constraint and have it actually work? (because copying the N < 128 expression won't make it work, initially)
You could use trait Lt128<const N: usize> = IsTrue<{N < 128}>;, I guess.
There's also the option of just writing where [(); 128 - N],, I think (but I'm not sure we guarantee that will panic).

@rodrimati1992 You can probably just do (): IsTrue<{N < 128}> without a separate type.
I guess you want to reuse the constraint and have it actually work? (because copying the N < 128 expression won't make it work, initially)
You could use trait Lt128<const N: usize> = IsTrue<{N < 128}>;, I guess.
There's also the option of just writing where [(); 128 - N],, I think (but I'm not sure we guarantee that will panic).

So,with trait aliases I could rewrite is to this?:

trait AssertLessThan128<const N:usize>=
    Assert<{N<=128}, (
        Str<"uint cannot be constructed with a size larger than 128,the passed size is:",
        Usize<N>>
    ) >;

The idea with the Assert trait is using a type error to print an error message embedded in a type,which in the case of AssertLessThan128 is:

(Str<"uint cannot be constructed with a size larger than 128,the passed size is:",Usize<N>>)

which I expect to be more helpful than where [(); 128 - N],,since it tells you in the error message why the compile-time error happened.

You can also do if !(N < 128) { panic!("...") } in a constant, I think?

I thought the std::fmt architecture is not going to be there until const trait constraints (or something similar) arrive?

With this thing you can have error messages with multiple values in it (embedded in types).

Maybe it's better to wait for const generics to talk about this,since it'll be easier to show executable examples.

@rodrimati1992 see https://github.com/rust-lang/rfcs/blob/master/text/2345-const-panic.md, there's going to be a special case to get const panic!() before it would be possible via other features (at a guess it probably won't allow custom formatting of values at first).

@rodrimati1992 Ahh, I see, that's indeed a clever trick!

What's the status of this?

This is correct. To give a quick update for those not following the PR #53645, const generics have compile and work in at least one simple usecase. What remains is finishing up codegen for other usecases, including const generics in arrys, and some error output cleanup. After that, the PR should be ready merge and people can start playing with it.

Might be off-topic, but will this allow a variant or fork of Chunks and related methods to have Item be a compile-time-fixed-size array instead of a slice? This would allow a for loop to destructure/bind to an irrefutable pattern that mapped the elements in the chunk to variables, such as for (first, second) in arr.chunks(2) which currently fails, and I'm guessing (without any justification admittedly) that it would allow more optimization in certain use cases.

@jeffvandyke You might be thinking of ChunksExact specifically. Such a change would be API-breaking, so it can't be done. We could add new APIs that give array references in the future, but we can't break existing ones.

@jeffvandyke You might be thinking of ChunksExact specifically. Such a change would be API-breaking, so it can't be done. We could add new APIs that give array references in the future, but we can't break existing ones.

I'm only waiting for this to be implemented and stabilized before proposing a PR that adds such API in addition to ChunksExact and its variants :)

The runtime and compile-time variants both have their use-cases, you don't always know your chunk size ahead of time. Optimization-wise, if you use ChunksExact with a constant it should be more or less the same according to my testing. The compiler can optimize away all bounds checks.

to be implemented and stabilized

I'd suggest not waiting for stabilization as such an API would be one more good use to help exercise this feature for stabilization.

I'm guessing that this doesn't yet work for impl blocks? I tried

#![feature(const_generics)]

struct The<const Val: u64>();

impl<const Val: u64> The<Val> {
    fn the() {
        println!("{}", Val);
    }
}

but, as was indeed warned, the compiler crashed with

thread 'rustc' panicked at 'slice index starts at 1 but ends at 0', src/libcore/slice/mod.rs:2419:5
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
stack backtrace:
   0: std::sys::unix::backtrace::tracing::imp::unwind_backtrace
   1: std::sys_common::backtrace::_print
   2: std::panicking::default_hook::{{closure}}
   3: std::panicking::default_hook
   4: rustc::util::common::panic_hook
   5: std::panicking::rust_panic_with_hook
   6: std::panicking::continue_panic_fmt
   7: rust_begin_unwind
   8: core::panicking::panic_fmt
   9: core::slice::slice_index_order_fail
  10: rustc_resolve::Resolver::resolve_ident_in_lexical_scope
  11: rustc_resolve::Resolver::resolve_path
  12: rustc_resolve::Resolver::resolve_path_without_parent_scope
  13: rustc_resolve::Resolver::smart_resolve_path_fragment
  14: rustc_resolve::Resolver::smart_resolve_path
  15: <rustc_resolve::Resolver<'a> as syntax::visit::Visitor<'tcx>>::visit_ty
  16: syntax::visit::walk_generic_args
  17: syntax::visit::walk_ty
  18: rustc_resolve::Resolver::with_generic_param_rib
  19: rustc_resolve::Resolver::resolve_item
  20: rustc_resolve::Resolver::resolve_crate
  21: rustc::util::common::time
  22: rustc_interface::passes::configure_and_expand_inner
  23: rustc_interface::passes::configure_and_expand::{{closure}}
  24: <rustc_data_structures::box_region::PinnedGenerator<I, A, R>>::new
  25: rustc_interface::passes::configure_and_expand
  26: <rustc_interface::queries::Query<T>>::compute
  27: <rustc_interface::queries::Query<T>>::compute
  28: <rustc_interface::queries::Query<T>>::compute
  29: rustc_interface::queries::<impl rustc_interface::interface::Compiler>::prepare_outputs
  30: rustc_interface::interface::run_compiler_in_existing_thread_pool
  31: <std::thread::local::LocalKey<T>>::with
  32: <scoped_tls::ScopedKey<T>>::set
  33: syntax::with_globals

The error is related to the const in impl<const Val: u64> since removing that part causes other errors but doesn't crash.

but props to Rust for even considering this feature. I had no idea if it'd work but the syntax seemed natural, I went for it, and lo rustc said it existed :)

I'm not surprised that it is not working, as this hotly anticipated feature is still being plumbed through the compiler as we speak (see e.g. #59008 and #58581 and the earlier work on #53645 which was abandoned because the PR was too big, but still kept open as a tracker to announce progress).

However, I'm not sure if out-of-bounds slice accesses should be expected of the current implementation stubs. @varkor @yodaldevoid, can you have a look?

Yes, the warning is correct: const generics are not yet functional in any form. There are still a few more pull requests before they're ready to start getting played around with.

Sorry if this is not the right place to ask questions but I couldn't find anywhere better. Just 2 questions:

  1. Can a single function be conditionally const? For example, a function could have 2 signatures like:

    const fn foo<A: const T>(x: T)  // `A` const implements `T`
    fn foo<A: T>(x: A)              // `A` implements `T` normally
    

    In this case, foo is const iff A: const T; if A does not const implement T, it still satisfies the bound but foo is no longer const. It is also important for the author to be able to specify any generic bound for more complex examples (ex. where A::Output : Bar). A great example of this is even simple arithmetic:

    // This only accepts types that const implement `T`
    const fn square_const<T: const Mul>(x: T) -> T {
      x*x
    }
    
    // This accepts types that implement `T` any way, but it is not const
    // This function behaves identically to `square_const`
    // But has a different signature and needs a different name
    fn square<T: Mul>(x: T) -> T {
      square_const(x)
    }
    
    let a: u8 = 5;
    let b: FooNumber = FooNumber::new();
    square_const(a); // `u8` const implements Mul
    square(b); // `FooNumber` implements `Mul` normally, so we need a separate function?
    

    I feel strongly that there should definitely be a way to do this, and I'm surprised that it is not mentioned in the RFC (unless I missed it?).

  2. _[less important]:_ Will there be a way to detect in the body of a const function whether we are running at compile-time or run-time? I think that some macro similar to cfg!() would be a useful extension, if for no other reason than debugging. cfg!() is currently evaluated at compile-time already, so I think(/guess) that it should be able to know whether or not the function is being used as const or a regular function since that too is determined at compile-time. However this is less important than my 1st question.

@Coder-256

  1. Yes, see https://github.com/rust-lang/rfcs/pull/2632.
  2. I am not sure whether that should even be possible, though given the above I'm not sure it's necessary either.

@Coder-256 Both of these questions are not related to const generics, but rather const functions. Const generics are for being generic over consts (e.g. foo<2>()) rather than functions being able to be run at compile-time. I imagine this is why you did not find the answers to your questions in RFC 2000.

@rpjohnst Thank you, but I think I might have been unclear. I have already seen both rust-lang/rfcs#2632 and rust-lang/rfcs#2000, but I'm pretty sure that this is not mentioned in either. (but I could be wrong?) What I am asking about is conditionally const functions. See the examples I wrote since it is hard to describe.

@yodaldevoid Whoops you're right, where should I ask this?

As for the macro question, I agree that there isn't really much use for it now that I think about it

What I am asking about is conditionally const functions. See the examples I wrote since it is hard to describe.

Your square_const definition can be used in place of square (that is: it is coerced to a function with the equivalent signature at run-time). See https://github.com/rust-lang/rfcs/pull/2632 for discussion of this behaviour (also, that thread is the right place to ask questions about any interactions between const fn and trait bounds).

@varkor I'm not convinced that that's the case (since the trait bounds change), but I'll ask in rust-lang/rfcs#2632.

Crash Report:

Code:

#![feature(const_generics)]

use std::marker::PhantomData;

struct BoundedU32<const LOWER: u32, const UPPER: u32> {
    value: u32,
    _marker: PhantomData<(LOWER, UPPER)>,
}

Compiler:

thread 'rustc' panicked at 'slice index starts at 1 but ends at 0', src/libcore/slice/mod.rs:2419:5
note: Run with `RUST_BACKTRACE=1` environment variable to display a backtrace.

error: internal compiler error: unexpected panic

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

note: rustc 1.35.0-nightly (719b0d984 2019-03-13) running on x86_64-unknown-linux-gnu

note: compiler flags: -C codegen-units=1 -C debuginfo=2 --crate-type lib

note: some of the compiler flags provided by cargo are hidden

error: Could not compile `playground`.

To learn more, run the command again with --verbose.

@Jezza: const generics isn't fully implemented yet and is not expected to work. We'll make an announcement when it's time to start experimenting with #![feature(const_generics)] (which you'll be notified about if you're subscribed to this issue).

@varkor Hang on, @Jezza's example has _marker: PhantomData<(LOWER, UPPER)> - those are two const parameters used as types, why didn't rustc_resolve produce an error?

Good point: I'll investigate this. Note that this is only a problem with #![feature(const_generics)], so it's not a critical issue (using non-generic consts produces the error as expected). Maybe it never reaches rustc_resolve.

~@eddyb: this ICE is coming from resolve_ident_in_lexical_scope, so I imagine it's probably related to https://github.com/rust-lang/rust/issues/58307.~

Edit: actually, maybe not ā€” that seems to apply just to macro_rules!.

Minimized:

#![feature(const_generics)]

struct S<const C: u8>(C);

Resolve ICEs before producing the "expected type, found value" error.

The index is out-of-bounds here:
https://github.com/rust-lang/rust/blob/master/src/librustc_resolve/lib.rs#L3919

Current nightly produces "parameter N is never used" for the following code:

struct Foo<const N: usize> { }

From the previous discussion I thought compile should accept this code. Is it simply an artifact of the unfinished implementation?

@newpavlov normal type parameters need to be used, should one be able to do PhantomData<n>?

I guess it's already possible to do PhantomData<[(); N]>. Not sure that's something we actually want to enforce, though, as AFAIU the point of PhantomData is to mark variance, and there's AFAIU no notion of variance wrt. a const generic parameter.

And that only works when N is of type usize.

We did decide that it was not necessary to use const parameters during the RFC discussion and the current implementation is a bug.

@withoutboats Can you point out where in the RFC that is stated? I tried finding something to that effect and must have missed it.

i dont know if it was included in the RFC text

@withoutboats Would you mind pointing out where that was discussed? I trawled through the RFC PR, but it didn't jump out at me.

@yodaldevoid This comment was referred to earlier : https://github.com/rust-lang/rust/issues/44580#issuecomment-419576947 . But it was not in the RFC PR, rather on this issue.

I can't trawl through the comment history from several years ago now, but I can explain: using type variables is required as a blocker to make you think about variance of those parameters (IMO this is also unnecessary and we could default to covariant, but thats a separate issue). Const parameters do not have any interaction with variance, so this would have no motivation.

@HadrienG2 Thank you for finding a relevant comment.

@withoutboats I did not really expect you to go trawling through all of the years of comments for this, it was only a hope that you had it already in hand.

Thank you for the explanation. I must admit that I can never wrap my mind around variance no matter how many times I try to learn it, but even without that when thought on again not requiring the use of cost parameters makes sense. I will throw fixing this bug onto our list of FIXMEs.

Here's a summary of progress so far on const generics.


Before work on const generics could properly begin, there was some refactoring that needed to be done. @jplatte took on the task with https://github.com/rust-lang/rust/pull/45930. @jplatte then started working on the main implementation of const generics, but found they didn't have enough time to continue.

@yodaldevoid and I picked up where @jplatte left off, but quickly found that making progress was stymied by the way generic parameters were handled in general. Thus began a series of changes to revamp the way generics were handled throughout the codebase: https://github.com/rust-lang/rust/pull/48149, https://github.com/rust-lang/rust/pull/48452, https://github.com/rust-lang/rust/pull/48523, https://github.com/rust-lang/rust/pull/51880.

With that done, the implementation of const generics could begin in earnest. Since then, @yodaldevoid and I have slowly but surely been incrementally adding support for const generics: https://github.com/rust-lang/rust/pull/58191, https://github.com/rust-lang/rust/pull/58503, https://github.com/rust-lang/rust/pull/58581, https://github.com/rust-lang/rust/pull/58583, https://github.com/rust-lang/rust/pull/59170, https://github.com/rust-lang/rust/pull/59355, https://github.com/rust-lang/rust/pull/59415, https://github.com/rust-lang/rust/pull/60058, https://github.com/rust-lang/rust/pull/60280, https://github.com/rust-lang/rust/pull/60284 and most recently https://github.com/rust-lang/rust/pull/59008. (These have mostly been split out from the main const generics pull request.)


What's the status now? Some const generics tests now work šŸŽ‰However, there are still those that don't, and there are a number of FIXMEs throughout the code that we still have to address. We're getting close now, though, and we're at the stage where there's some low-hanging fruit if you want to help.

  • First, we need more tests. There are only a handful of const generics tests so far, but we'd like many more. At the moment, because we're aware of a number of issues, we don't need bug reports, but we do need passing tests. If you come up with a test case that works (and doesn't look too similar to an existing test), feel free to open a pull request to add it.
  • Secondly, as mentioned, there are a number of FIXME(const_generics) scattered throughout the code. We're planning on working our way through them, but @yodaldevoid and I only have so much time, so if you think you can tackle one, go ahead (though you might like to leave a comment saying as much, so we don't duplicate efforts).

I've written an overview of some of the remaining implementation issues before const generics will be ready for proper testing, in the top post, to give a rough idea of what's left to do.

It's taken time, but we've steadily been making progress and hope to keep up the pace. It's motivating to finally see things starting to fall into place!

I've been expecting months for this post @varkor :) I'm no Rust wizard but I'd like to tackle one of the FIXMEs

Congratulations @jplatte, @yodaldevoid and @varkor. This is a huge step to get rid of custom Array-like traits in math libs.

Cross-posting...

@varkor Regarding tests, perhaps it would help to know what is expected to work. For example, I was surprised that this does not work: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=d84ffd15226fcffe02c102edb8ae5cf1

Also, for reference of those interested in FIXMEs: https://oli-obk.github.io/fixmeh/

@mark-i-m try putting {} around FOO. That way it will work.

Quoting https://github.com/rust-lang/rfcs/blob/master/text/2000-const-generics.md:

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.

{expression} should only be necessary if the expression is not an identifier or a literal,that is a bug.

Quoting the same RFC:

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.

@mark-i-m Yes, currently we cannot tell the difference between idents for types and consts when initially parsing. We punted the decision on how to handle all that into the future. I suppose now might be the future.

For a little background, we have talked about two ways that I can remember about how to handle this. The first way is to force people to mark const arguments (either by typing const before them or some other way). This isn't great from an ergonomics point of view, but it is easy from a parsing point of view. The second way is to treat all generic arguments as the same until we start pairing them up with generic parameters later during compilation. This is probably the method we want to take, and there has been some work to make this possible already, we just haven't taken the final leap.

Amazing work. I'm happy to help out, by fixing some of the FIXME's, if I can. I don't have much experience within the rustc codebase at all, so I'll start on the FIXME https://github.com/rust-lang/rust/blob/master/src/librustc_mir/monomorphize/item.rs#L397, as it's seems like it would be an easy one.

What about the following code:

trait Foo {
    const N: usize;
    fn foo() -> [u8; Self::N];
}

Currently it results in "no associated item named N found for type Self in the current scope" compilation error. Will such code be accepted after finishing FIXMEs or will it require additional effort to implement?

@yodaldevoid

Quick question, apologies if this has already been discussed.

The second way is to treat all generic arguments as the same until we start pairing them up with generic parameters later during compilation. This is probably the method we want to take, and there has been some work to make this possible already, we just haven't taken the final leap.

Does this not run rather against the grain on the principle Rust takes on making function signatures explicit to avoid producing errors that relate to a function's implementation? Perhaps I'm totally misunderstanding this however and you're talking about parsing generic parameters within a function's body.

Does this not run rather against the grain on the principle Rust takes on making function signatures explicit to avoid producing errors that relate to a function's implementation? Perhaps I'm totally misunderstanding this however and you're talking about parsing generic parameters within a function's body.

This is about determining whether identifiers passed as generic parameters to a function are constants or types.

Example:

fn greet<const NAME:&'static str>(){
    println!("Hello, {}.",NAME);
}
const HIS_NAME:&'static str="John";
greet::<HIS_NAME>();
greet::<"Dave">();

Note that when defining the function,you have to specify that NAME is a constant,but when you call it,it's not necessary to say that HIS_NAME is a constant within the turbofish operator (::< >) .

@zesterer If this proposed solution was made transparent to the user (e.g. when defining a function there was no difference between parameter types) you would be correct. However the idea behind the proposed solution is not to change the user facing behavior, but rather to only change the implementation. The user would still write out function signatures with explicit type, const, and lifetime parameters, and generic arguments would still need to match up to a corresponding parameter, just how we parse it in the compiler would change.

@newpavlov I am surprised that code doesn't work. If you could submit a separate issue it would be appreciated.

@yodaldevoid @robarnold

Ah, got you. I wrongly assumed this is was relating to type/function signatures.

For using const generics on built-in array types, I've opened #60466 to see others' opinions on this.

Impl blocks seem completely broken at the moment. Is this expected in the current state of the feature, or should I open another issue about it?

Impl blocks seem completely broken at the moment. Is this expected in the current state of the feature, or should I open another issue about it?

You need to add {}s around the N, impl<const N: usize> Dummy<{N}> {}.

But yeah then you'll get an error about the unconstrained parameter.

Ah, thanks. Forgot about the braces thing !

I'm not surprised that it fails once that is resolved, since this was a heavily minimized test case.

...but yeah, this one should probably work:

#![feature(const_generics)]

trait Dummy {}

struct Vector<const N: usize> {
    data: [f32; N],
}

impl<const N: usize> Dummy for Vector<{N}> {}

...and it still fails with E0207:

warning: the feature `const_generics` is incomplete and may cause the compiler to crash
 --> src/lib.rs:1:12
  |
1 | #![feature(const_generics)]
  |            ^^^^^^^^^^^^^^

error[E0207]: the const parameter `N` is not constrained by the impl trait, self type, or predicates
 --> src/lib.rs:9:12
  |
9 | impl<const N: usize> Dummy for Vector<{N}> {}
  |            ^ unconstrained const parameter

error: aborting due to previous error

For more information about this error, try `rustc --explain E0207`.
error: Could not compile `small-matrix`.

To learn more, run the command again with --verbose.

...and I assume this is what @varkor meant by "issues with array handling for consts generics" in #60466:

#![feature(const_generics)]

fn dummy<const N: usize>() -> [f32; N] {
    [0.; N]
}

->

warning: the feature `const_generics` is incomplete and may cause the compiler to crash
 --> src/lib.rs:1:12
  |
1 | #![feature(const_generics)]
  |            ^^^^^^^^^^^^^^

error: internal compiler error: cat_expr Errd
 --> src/lib.rs:3:44
  |
3 |   pub fn dummy<const N: usize>() -> [f32; N] {
  |  ____________________________________________^
4 | |     [0.; N]
5 | | }
  | |_^

error: internal compiler error: cat_expr Errd
 --> src/lib.rs:4:5
  |
4 |     [0.; N]
  |     ^^^^^^^

error: internal compiler error: QualifyAndPromoteConstants: Mir had errors
 --> src/lib.rs:3:1
  |
3 | / pub fn dummy<const N: usize>() -> [f32; N] {
4 | |     [0.; N]
5 | | }
  | |_^

error: internal compiler error: broken MIR in DefId(0/0:3 ~ small_matrix[5b35]::dummy[0]) ("return type"): bad type [type error]
 --> src/lib.rs:3:1
  |
3 | / pub fn dummy<const N: usize>() -> [f32; N] {
4 | |     [0.; N]
5 | | }
  | |_^

error: internal compiler error: broken MIR in DefId(0/0:3 ~ small_matrix[5b35]::dummy[0]) (LocalDecl { mutability: Mut, is_user_variable: None, internal: false, is_block_tail: None, ty: [type error], user_ty: UserTypeProjections { contents: [] }, name: None, source_info: SourceInfo { span: src/lib.rs:3:1: 5:2, scope: scope[0] }, visibility_scope: scope[0] }): bad type [type error]
 --> src/lib.rs:3:1
  |
3 | / pub fn dummy<const N: usize>() -> [f32; N] {
4 | |     [0.; N]
5 | | }
  | |_^

thread 'rustc' panicked at 'no errors encountered even though `delay_span_bug` issued', src/librustc_errors/lib.rs:356:17
note: Run with `RUST_BACKTRACE=1` environment variable to display a backtrace.

error: internal compiler error: unexpected panic

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

note: rustc 1.36.0-nightly (cfdc84a00 2019-05-07) running on x86_64-unknown-linux-gnu

note: compiler flags: -C debuginfo=2 -C incremental --crate-type lib

note: some of the compiler flags provided by cargo are hidden

@HadrienG2
See #60619 and #60632.

I think this code should build:
https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=e45b7b5e881732ad80b7015fc2d3795c

but it currently errors:

error[E0119]: conflicting implementations of trait std::convert::TryFrom<[type error]> for type MyArray<_, _>:

Unfortunately that's a restriction of Try{From,Into} and doesn't have anything to do with const generics. They can't be implemented generically in many cases: playpen

@oberien in your example, T is a foreign type, and it's not known whether T: Into<Foo<T>> holds or not.

In my example, [T; N] is a type defined in libcore, and is #[fundamental](tbh i don't even know what this means), so i think the trait resolver actually knows [T; N]: Into<MyArray<T, {N}>> does not hold, and there should not be a conflict, i think?

[...] and is #[fundamental] [...]

This is the issue, fundamental is related to allowing downstream users to define trait implementations when they wrap a local type in the fundamental wrapper type. You can see in this playground that your implementation works between non-fundamental types, but fails if the from type is marked fundamental (with a much better error message).

@varkor @yodaldevoid So, I just ended up in a situation where S<{N == 0}> (with S taking a const bool parameter and N a const usize) is neither S<{true}> or S<{false}> in the eye of the compiler (in the sense that it does not implement the same traits as either), and am not sure if this is a bug or an expected limitation of the current prototype. Can you give me a quick refresher on the current unification logic?

As a minimized example, this...

// Machinery for type level if-then-else
struct Test<const B: bool>;
trait IfFn<S, Z> { type Out; }
impl<S, Z> IfFn<S, Z> for Test<{true }> { type Out = S; }
impl<S, Z> IfFn<S, Z> for Test<{false}> { type Out = Z; }

// Returns an u8 if B is true, else an u16
fn should_be_ok<const B: bool>() -> <Test<{B}> as IfFn<u8, u16>>::Out {
    0
}

...produces the following error:

error[E0277]: the trait bound `Test<B>: IfFn<u8, u16>` is not satisfied
  --> src/lib.rs:32:1
   |
32 | / fn should_be_ok<const B: bool>() -> <Test<{B}> as IfFn<u8, u16>>::Out {
33 | |     0
34 | | }
   | |_^ the trait `IfFn<u8, u16>` is not implemented for `Test<B>`
   |
   = help: the following implementations were found:
             <Test<false> as IfFn<S, Z>>
             <Test<true> as IfFn<S, Z>>

I don't actually know, but I think the problem may be that the compiler doesn't have the logic to decide if the 0 literal is a u8 or a u16. Try something like: 0u8 as _

That's not it, the error message remains the same.

But if you want a version that does not involve integer type inference, here's a sillier minimization:

struct Test<const B: bool>;
trait Not { const B: bool; }
impl Not for Test<{true }> { const B: bool = false; }
impl Not for Test<{false}> { const B: bool = true; }

fn should_be_ok<const B: bool>() -> bool {
    <Test<{B}> as Not>::B
}

Still fails due to Test<{B}> allegedly not implementing Not.

Hi ! I'm not really sure whether unification on const values is supposed to work or not, but there is a new unification system in the works ("chalk") that is being worked on but isn't what rustc currently uses.
So what you're trying to do _may_ have to wait until chalk is ready. And even then, I'm not sure it will work or be supposed to work even with chalk.
See https://github.com/rust-lang/rust/issues/48049 for progress.

@HadrienG2 could you file a separate issue for that? I'm not entirely surprised that doesn't work at the moment due to a few other issues that we are looking into, but this is a new case and I'd like to have it tracked.

@carado Unification is done on consts or none of this would work. Const generics are not blocked on chalk.

@HadrienG2 once we have specialization for const generics, you'll be able to do something like

struct Test<const B: bool>;
trait Not { const B: bool; }
impl<const B: bool> Not for Test<B> { const B: bool = false; }
impl Not for Test<{false}> { const B: bool = true; }

fn should_be_ok<const B: bool>() -> bool {
    <Test<{B}> as Not>::B
}

What you want to do is some kind of exhaustiveness check like there is for match just extended to the trait system. Don't know of it being mentioned in any thread.

@carado Chalk doesn't concern the actual "typesystem primitives", which this is one of.
That is, rustc still has to (even today, since Chalk is already partly integrated) implement the part of the unification which deals with entities opaque to Chalk (such as two different constant expressions).
If we implement that (which we don't have any near-future plans for, anyway), it won't change much even after Chalk replaces the trait system (which is its main purpose, not unification).

Oh, my bad then ! I guess I don't really know what I'm talking about.

One of the awesome things that const generics would provide is compile time computed simple functions, for example factorial.

Example:

fn factorial<const X: i32>() -> Option<i32> {
    match X {
        i if i < 0 => None,
        0 => Some(1),
        1 => Some(1),
        i => Some(factorial::<i - 1>().unwrap() + i)
    }
}

As far as I understand, there is some limited support for const generics in nightly? If so, is there any place more organised than this issue where I can find how to use it and stuff?

@dancojocaru2000 const functions should be the preferred way for value-level computation at compile-time, e.g. https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=4994b7ca9cda0bfc44f5359443431378, which happens not to work because they're not fully implemented yet. But neither are const generics.

You snippet might be problematic because I'm not sure whether match is supposed to work for const arguments. You also mixed addition with multiplication in the code, but that doesn't matter too much.

I think you can already do factorials at compile-time with a Peano encoding, which is often shown as a demo for other functional languages.

compile time computed simple functions

I think the more appropriate feature would be const fn.

Theoretically, your code would work almost as-is

#![feature(const_generics)]

fn factorial<const X: i32>() -> Option<i32> {
    match X {
        i if i < 0 => None,
        0 => Some(1),
        1 => Some(1),
        i => Some(factorial::<{X - 1}>().unwrap() + i)
    }
}

fn main() {
    println!("{:?}", factorial::<10>());
}

But it doesn't:

warning: the feature `const_generics` is incomplete and may cause the compiler to crash
 --> src/main.rs:1:12
  |
1 | #![feature(const_generics)]
  |            ^^^^^^^^^^^^^^

error: internal compiler error: src/librustc_codegen_ssa/mir/operand.rs:79: unevaluated constant in `OperandRef::from_const`

Although you really should use an unsigned type:

#![feature(const_generics)]

fn factorial<const X: u32>() -> u32 {
    match X {
        0 => 1,
        1 => 1,
        _ => factorial::<{ X - 1 }>() + X,
    }
}

fn main() {
    println!("{:?}", factorial::<10>());
}

is there any place more organised than this issue where I can find how to use it and stuff?

That's usually covered by the unstable book, but there's nothing useful there now. As you discover bits, perhaps you could consider starting to sketch out some content for that?

factorials at compile-time with a Peano encoding,

For an example of that, see the typenum crate

Theoretically, your code would work almost as-is

When calling factorial::<0> the _ => factorial::<{X - 1}>() * X would be codegened too right? But attempting to do so would cause an integer underflow.

Can I expect code like this to compile in the future?

#![feature(const_generics)]

trait NeedsDrop<const B: bool> { }
impl<T> NeedsDrop<std::mem::needs_drop<T>()> for T { }

fn foo<D: NeedsDrop<false>>(d: D) { }

This is crossing with a recent implementation of some [T; N] arrays for N <= 32 as const generics, but the following code does not compile on a latest (4bb6b4a5e 2019-07-11) nightly with various errors the trait `std::array::LengthAtMost32` is not implemented for `[u64; _]`

#[derive(Copy, Clone, PartialEq, Eq)]
pub struct BigintRepresentation<
    const N: usize
>(pub [u64; N]) 
where [u64; N]: std::array::LengthAtMost32, 
   [u64; N*2]: std::array::LengthAtMost32;

although the following does:

#[derive(Copy, Clone, PartialEq, Eq)]
pub struct BigintRepresentation<
    const N: usize
>(pub [u64; N]) 
where [u64; N]: std::array::LengthAtMost32;

Looks like N*2 is not a valid const or qualifier for such constraint

@shamatar N*2 should be surrounded with curly braces like `[u64; {N*2}]

I think the "trait not implemented" errors come from the derive macros not adding the bound for [u64; {N*2}], but if the derives are removed there is an ICE currently:

```error: internal compiler error: constant in type had an ignored error: TooGeneric
--> src/main.rs:5:1
|
5 | / pub struct BigintRepresentation<
6 | | const N: usize
7 | | >(pub [u64; N])
8 | | where [u64; N]: std::array::LengthAtMost32,
9 | | [u64; {N*2}]: std::array::LengthAtMost32;
| |____________________________________________^

thread 'rustc' panicked at 'no errors encountered even though delay_span_bug issued', src/librustc_errors/lib.rs:366:17
note: run with RUST_BACKTRACE=1 environment variable to display a backtrace.

This code does not compile:

#![feature(const_generics)]

struct Foo<const X: usize>([u8; X]);

impl<const X: usize> Foo<X> {
    fn new() -> Self {
        Self([0u8; X])
    }
}
warning: the feature `const_generics` is incomplete and may cause the compiler to crash
 --> src/lib.rs:1:12
  |
1 | #![feature(const_generics)]
  |            ^^^^^^^^^^^^^^

error[E0573]: expected type, found const parameter `X`
 --> src/lib.rs:5:26
  |
5 | impl<const X: usize> Foo<X> {
  |                          ^
  |                          |
  |                          not a type
  |                          help: try using the variant's enum: `regex_syntax::ast::HexLiteralKind`

error[E0107]: wrong number of const arguments: expected 1, found 0
 --> src/lib.rs:5:22
  |
5 | impl<const X: usize> Foo<X> {
  |                      ^^^^^^ expected 1 const argument

error[E0107]: wrong number of type arguments: expected 0, found 1
 --> src/lib.rs:5:26
  |
5 | impl<const X: usize> Foo<X> {
  |                          ^ unexpected type argument

error: aborting due to 3 previous errors

@npmccallum you need to do Foo<{X}>

@pengowen123 Yes, it compiles (crashes the compiler with constant in type had an ignored error: TooGeneric) without derive, but then it may be a separate question whether or not such "double constraint" that is effectively N <= 32 and N <= 16 is not applied by compiler.

Expressions that mention parameters probably won't work for a while longer, [T; N] and Foo<{N}> are special-cased to not have expressions with a mention of N in them, but rather directly refer to the N parameter, bypassing the larger issue.

Using Self causes a crash.
Even swapping it out for Value<{C}>, it still crashes.

#![feature(const_generics)]

struct Value<const C: usize>;

impl<const C: usize> Value<{C}> {
    pub fn new() -> Self {
        unimplemented!()
    }
}

pub fn main() {
    let value = Value::new();
}

Same as #61338, the source of problem is incremental compilation.

compile time computed simple functions

I think the more appropriate feature would be const fn.

Theoretically, your code would work almost as-is

#![feature(const_generics)]

fn factorial<const X: i32>() -> Option<i32> {
    match X {
        i if i < 0 => None,
        0 => Some(1),
        1 => Some(1),
        i => Some(factorial::<{X - 1}>().unwrap() + i)
    }
}

fn main() {
    println!("{:?}", factorial::<10>());
}

But it doesn't:

warning: the feature `const_generics` is incomplete and may cause the compiler to crash
 --> src/main.rs:1:12
  |
1 | #![feature(const_generics)]
  |            ^^^^^^^^^^^^^^

error: internal compiler error: src/librustc_codegen_ssa/mir/operand.rs:79: unevaluated constant in `OperandRef::from_const`

Although you really should use an unsigned type:

#![feature(const_generics)]

fn factorial<const X: u32>() -> u32 {
    match X {
        0 => 1,
        1 => 1,
        _ => factorial::<{ X - 1 }>() + X,
    }
}

fn main() {
    println!("{:?}", factorial::<10>());
}

This feature is useful for me too, const fn isn't enough. I want to use it for an N-dimensional array with a terminating condition of dimensions=0.

I am able to create a struct:

#![feature(const_generics)]
struct MyArray<T: Default, const len: usize> {
    real_array: [T; len]
}

impl<T: Default, const len: usize> MyArray<T, {len}> {
    fn new() -> Self {
        return MyArray {
            real_array: [Default::default(); len]
        }
    }
}

This fails because I can't actually initialize the array due to:

error: array lengths can't depend on generic parameters
 --> src/main.rs:9:46
  |
9 |             real_array: [Default::default(); len]
  |                                              

I've tried working around it by making const values set to the generic value.

That gets past the above error, but the compiler then crashes.

error: internal compiler error: src/librustc/ty/subst.rs:597: const parameter `height/#0` (Const { ty: usize, val: Param(height/#0) }/0) out of range when substituting substs=[]

The language desperately needs basic constant generics, such that the standard library doesn't have to define each function for each size of array. This is the only reason I don't use rust. Do we really need turing complete compile time functions?
I might be wrong but simple integer expressions should suffice and I hope nobody is wasting their time, making sure that these wacky examples work.

The language desperately needs basic constant generics, such that the standard library doesn't have to define each function for each size of array

There is already some effort there https://github.com/rust-lang/rust/issues/61415.

I hope nobody is wasting their time, making sure that these wacky examples work.

Some people do, I cannot see any problem with that ;)

This is the only reason I don't use rust.

This is the least interesting reason I've seen quoted for someone not using rust. Are you sure you're telling the truth?

but simple integer expressions should suffice

Making even that reduced scope is incredibly hard. Try your hand at it, we have really capable people making this and there's a reason why it's taking this long.

Unfortunately, I've not been able to dedicate much time to fixing const generics bugs recently (or to Rust more generally). If anyone wants to get involved in pushing const generics along, I'd be happy to offer advice for tackling the open issues and review bug fixes, though it'll probably be a little while before I can concentrate on this again.

I hope nobody is wasting their time, making sure that these wacky examples work.

Nobody is, miri is already far more powerful than C++ constexpr.
And nobody is working on anything fancy like deriving N = M from N + 1 = M + 1.

Most of these bugs aren't about the expressions in them, they're about the typesystem and how const generics interact with all other features.
They would still be there if all you had were const generics and integer literals.

Btw, I think the "array lengths can't depend on generic parameters" error for [expr; N] expressions aren't needed, we could use the same trick that we do for [T; N] types, and pull out the N without evaluating it as an expression.

While I do want to take a stab at this, I'm not sure I'm the right person. I've used little rust and know very little compiler theory. I might need a fair bit of coaching but I'm certainly willing. šŸ˜„

Edit: I have a fair bit of experience in software in general though.

@varkor, I've been looking for something useful to do on rustc, and I'd love to step up and help out. Also, thanks for being candid about your ability to allocate time to it!

@varkor, I'm actually following through on the above comment, so if you have any advice, I'm all ears! Feel free to refer me to another communication channel, too.

One thing we could do right now, I just realized is allow Foo::<{...}> / [expr; ...] expressions to refer to generic parameters (in the ... part).

This is because expressions must be nested somewhere within a body, and that tends to prevent cyclic dependencies.

However, I'm worried that having e.g. [(); [0; 1][0]] in a signature would break, so we'd probably need a crater run anyway (and nowadays those take forever).

For anyone who's interested in helping out with const generics, my advice would be to take a look at the list of open const generics issues and investigate whichever looks interesting to you. Some have had some investigation already, which should be in the comments. Most of the issues are probably going to require a little digging. It's helpful to make a comment if you're planning to investigate something, so we don't duplicate efforts (but you can often ignore the issue assignee if there hasn't been any activity for a while). If you have any questions, you can ask in the issue comments, or on Discord or Zulip. @eddyb, @yodaldevoid, @oli-obk and I are familiar with many of the relevant areas and are good people to ask. Thanks for all your interest!

cc @hameerabbasi, @ranweiler

Questions about const generics:

  1. Where can I find documentation (so that I don't have to ask questions here)
  2. What limitations exist at this moment on using a const parameter of arbitrary type?
  3. What important features of const generics are unimplemented / crash the compiler?

P.S (Contributors:) Thank you so much for working on this feature.

const-generics is still under development so there aren't any official docs on it yet. I'm not too sure about the other two questions. When I last used const-generics they crashed when I specified some const-generic parameters, but it has been nearly a month since I did anything with it last so things may have changed since then.

And nobody is working on anything fancy like deriving N = M from N + 1 = M + 1.

This would be very useful to have a solver for such type constraints. For example when implementing an addition if two N-bits number, the return size should be (N + 1) to account for an overflow bit. When (for example) two 5-bit numbers are given the solver should check that N + 1 = 6. Hopefully this can be bolted onto const generics later :)

@flip111 Yes, I think the plan is to add this later, but this sort of general expression constraints are very complex and hard to implement. So we may not see them for at least a few years.

To be honest, solving those kinds of constraint-based problems sounds a lot like a job for Prolog. Maybe piggybacking on the chalk engine is an option? Afaik it solves Traits, though, right?

Btw I love the topic, although I can't afford to help with this atm. Thank to everyone who's working on Const Generics for your time and commitment. šŸ’

A small update: I'm working my way up the Rust tree. I do plan to contribute but want to self-study to the place where I don't need excessive coaching.

I am able to create a struct:

#![feature(const_generics)]
struct MyArray<T: Default, const len: usize> {
    real_array: [T; len]
}

impl<T: Default, const len: usize> MyArray<T, {len}> {
    fn new() -> Self {
        return MyArray {
            real_array: [Default::default(); len]
        }
    }
}

This fails because I can't actually initialize the array due to:

error: array lengths can't depend on generic parameters
 --> src/main.rs:9:46
  |
9 |             real_array: [Default::default(); len]
  |                                              

I ran into the same problem, but found a workaround using MaybeUninit:
https://play.rust-lang.org/?version=nightly&mode=release&edition=2018&gist=3100d5f7a4efd844954a6fa5e8b8c526
It's obviously just a workaround to get properly initialized arrays, but for me this is sufficient until a proper way is made available.
Note: I think the code should always work as intended but if someone finds a bug with the use of the unsafe, I would be happy to fix it.

@raidwas you are dropping uninitialized memory when you use the = operator to initialize uninitialized memory. Do this instead,

https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=5d962ce7c553e850030240244608ec00

@KrishnaSannasi thanks, good catch :D (technically I did not drop uninitialized memory since I only use it for primitives, but good to have a correct version here)

Technically, dropping even floats is undefined, but it isn't exploitable right now.

Technically, dropping even floats is undefined, but it isn't exploitable right now.

I would have expected that dropping any Copy type, even uninitialized ones, is a no-op. Maybe that's worth changing.

Technically, dropping even floats is undefined, but it isn't exploitable right now.

I would have expected that dropping any Copy type, even uninitialized ones, is a no-op. Maybe that's worth changing.

It's still technically UB. While safely dropping defined Copy values is a no-op, the compiler may decide to do some unexpected optimizations if you attempt to drop uninitialized memory of any type (e.g. remove all code that ever possibly touches that value), which could possibly break things. That's my understanding of it.

Not to be impolite, but ā‰„ 60 people get notified about comments on this thread, so we should probably keep the off-topic discussion to a bare minimum. Let's rather use this to coordinate the work on const generics as that's what we all want to happen.

Crashing if I used _aliases pointing to type with const-param_ from another crate (dependency).

For example:

  • crate A, lib
    ```#![crate_type = "lib"]

    ![feature(const_generics)]

pub type Alias = Struct;
pub struct Struct(T);

- crate B

extern crate crate_a;
use crate_a::Alias;

pub fn inner_fn(v: Alias) {}
```
crash-log (44580).txt

@fzzr- sounds like #64730

I've been switching some code to const generics and it seems like there are really two different use cases. I'm not sure if they should be conflated or if we'd be better served going with two different syntaxes for the use cases.

A lot of my usages are not actually for types where a constant value plays a role in determining the types, but rather to avail myself of features that are locked out for non-const/literal values (still not fully supported, e.g. match patterns, but ultimately will need to be).

IMHO we should formally land "const arguments" alongside const generics, so that people won't write mutant code introducing a thousand "const generics" (one for each argument) to get the compiler to evaluate certain variables as literals/constants.

@mqudsi Could you give an example? There are already plans and ongoing foundational work going on to make const eval more powerful. That's orthogonal to const generics, though.

What I mean is, if you want to refactor typical parser code like the following for reuse:

let mut result: u32 = 0;
let mut digits = 0;
while digits < max_digits && src.has_remaining() {
    match src.get_u8() {
        d@b'0'..=b'9' => {
            result = result*10 + (d - b'0') as u32;
            digits += 1;
        },
        b'>' => match result {
            0..=191 => break, // valid range
            _ => return Err(DecoderError::OutOfRange),
        },
        _ => return Err(DecoderError::Malformed)
    }
}

You would typically move it out to a function, except the following won't work because certain values that are used in pattern matching have become variables:

#[inline(always)]
fn read_str_digits<B: bytes::buf::Buf>(src: &mut B, stop_word: u8, 
    max_digits: u8, min_value: u32, max_value: u32) -> Result<u32, DecoderError> {
    let mut result: u32 = 0;
    let mut digits = 0;
    while digits < max_digits && src.has_remaining() {
        match src.get_u8() {
            d@b'0'..=b'9' => {
                result = result*10 + (d - b'0') as u32;
                digits += 1;
            },
            stop_word => match result {
                min_value..=max_value => break, // valid range
                _ => return Err(DecoderError::OutOfRange),
            },
            _ => return Err(DecoderError::Malformed)
        }
    }

    ...
}

If const generics lands before const arguments, I can suddenly abuse const generics to come up with the following:

#[inline(always)]
fn read_str_digits<const MinValue: u32, const MaxValue: u32, const StopWord: u8, B: bytes::buf::Buf>
(src: &mut B, max_digits: u8) -> Result<u32, DecoderError> {
    let mut result: u32 = 0;
    let mut digits = 0;
    while digits < max_digits && src.has_remaining() {
        match src.get_u8() {
            d@b'0'..=b'9' => {
                result = result*10 + (d - b'0') as u32;
                digits += 1;
            },
            StopWord => match result {
                MinValue..=MaxValue => break, // valid range
                _ => return Err(DecoderError::OutOfRange),
            },
            _ => return Err(DecoderError::Malformed)
        }
    }

    ...
}

As of today, even this code won't work because the compiler does not detect the const generic values to be constants valid for use in a pattern, but that is obviously incorrect and needs to be addressed before RFC 2000 can land. Don't get me wrong, I've been fighting for generic constants for years and I have PRs ready to go for a dozen major crates (I can't wait to make the timezone in chrono a const generic and unify all the various DateTime types), but imho if it becomes possible to abuse const generics to fake const arguments (where by "const" what we really mean is "literal") then you're going to see widespread abuse of that.

It's not necessarily the end of the world, but without having dug too deeply into it, it does seem as if a proper and complete const generics implementation will necessarily include all the plumbing for const arguments anyway, so we might as well take the extra time to finalize the syntax/ux/story for const arguments while we're at it and avoid an unfortunate era of code stink. Yes, the above can still be done with macros, but the ergonomics of const generics are a thousand times easier.

fwiw, this is how I'd imagine the const argument version to look like:

#[inline(always)]
fn read_str_digits<B: Bytes>(src: &mut B, min_value: const u32, 
    max_value: const u32, stop_word: const u8, max_digits: u8) -> Result<u32, ()> {
    let mut result: u32 = 0;
    let mut digits = 0;
    while digits < max_digits && src.has_remaining() {
        match src.get_u8() {
            d@b'0'..=b'9' => {
                result = result*10 + (d - b'0') as u32;
                digits += 1;
            },
            stop_word => match result {
                min_value..=max_value => break, // valid range
                _ => return Err(()),
            },
            _ => return Err(())
        }
    }

    ...
}

It would virtually be identical to const generics but for the semantics.

You would typically move it out to a function, except the following won't work because certain values that are used in pattern matching have become variables:

This seems like a use-case that is more readily addressed by allowing the values of bound variables to be used in patterns (not just consts), with some new syntax. The fact that consts are currently allowed in patterns is counter-intuitive and, as far as I'm aware, historical.

@mqudsi Forgive me if this is a silly question, but I don't see anything wrong about the example you gave. It looks like a perfectly valid use case for const generics to me: having a definition that is generalized to work with arbitrary values as the max/min. I don't really see the benefits of const arguments over const generics. They seem equivalent to me; that is, const arguments could just be implemented as a desugaring to const generics. Could you elaborate more on what's wrong with this design pattern?

Here's a summary of the work on const generics since the last update. Last time, it was all about the core implementation: making sure everything fitted together and getting some of the basic test cases passing. Since then, the effort has been focused on making const generics more reliable: fixing cases that crashed the compiler, or didn't work unexpectedly, or improving diagnostics. We're getting closer to something that works reliably, though there's still a way to go.


  • @skinny121 has been doing fantastic work in the last month fixing various outstanding issues with const generics:

    • Expanding the test suite (https://github.com/rust-lang/rust/pull/60550)

    • Fixing type inference issues (https://github.com/rust-lang/rust/pull/64679, https://github.com/rust-lang/rust/pull/65579)

    • Supporting more types in const generics, including strings and slices (https://github.com/rust-lang/rust/pull/64858) and pointers (https://github.com/rust-lang/rust/pull/64986) (though note that these are likely not to be stabilised immediately alongside the rest of const generics, as they're not supported in the original RFC)

    • Fixing diagnostic issues with const generics (https://github.com/rust-lang/rust/pull/65154, https://github.com/rust-lang/rust/pull/65579)

    • Fixing a number of issues with use of const generics cross-crate (https://github.com/rust-lang/rust/pull/65365)

  • @eddyb fixed some issues with const evaluation affecting const generics (https://github.com/rust-lang/rust/pull/63497)
  • @matthewjasper fixed some issues with using const generics in macros (https://github.com/rust-lang/rust/pull/63083)
  • @davidtwco fixed an issue involving const generics and constructors (https://github.com/rust-lang/rust/pull/60839)
  • @GuillaumeGomez fixed the display of const generics in Rustdoc (https://github.com/rust-lang/rust/pull/61605)
  • @varkor fixed some type inference issues (https://github.com/rust-lang/rust/pull/61570, https://github.com/rust-lang/rust/pull/60742, https://github.com/rust-lang/rust/pull/60508), an issue restricting the places const generics could be used (https://github.com/rust-lang/rust/pull/60717) and various compiler crashes (https://github.com/rust-lang/rust/pull/61380, https://github.com/rust-lang/rust/pull/61333, https://github.com/rust-lang/rust/pull/60710)

Additionally, other work on the compiler ended up fixing some of the other issues relating to const generics.

We've also started using const generics inside the compiler itself: array trait implementations now use const generics thanks to the work of @crlf0710 and @scottmcm (https://github.com/rust-lang/rust/pull/60466, https://github.com/rust-lang/rust/pull/62435), which led to performance improvements, and which will also allow us to unrestrict array trait implementations in the future (when const generics is stabilised). @Centril used the same approach to improve VecDeque (https://github.com/rust-lang/rust/pull/63061).


Many of the particularly common bugs with const generics have been fixed in the past few months. @nikomatsakis is investigating lazy normalisation, which should fix a host of the remaining issues, while @jplatte is looking into fixing the disambiguation of const parameters from type parameters (so you'll no longer have to type {X} for a const argument).

I also want to thank @eddyb for all their mentoring and reviewing for const generics throughout development, which has been invaluable.

There are still quite a number of other issues to address and, as before, we could use all the help we can get ā€”Ā if you're interested in tackling any of the remaining issues, feel free to ping me on the issue, or on Discord or Zulip for pointers on getting started.

@mqudsi @mark-i-m Would it be appropriate to extend the current syntactic alternative for generics, impl Trait in argument position, to const generics? This is the syntax @mqudsi suggested for this use case.

Personally I think this would improve the readability of such usecases, but I see one problem: normal generics are used to pass data, so they are bound to an argument. This is not the case for const generics, so how would you pass them? Pretend they are arguments?
In the case of impl Trait in argument position, it also cannot be used with the turbofish syntax. For const in argument, it would be the inverse. This may be confusing.

I'm not sure if the benefits outweigh the "weirdness" here, but I wanted to share my thoughts anyway.

I vaguely recalled a discussion about this before, and now I have found it: https://internals.rust-lang.org/t/pre-rfc-const-function-arguments/6709

@PvdBerg1998 that seems like a very bad idea. I really donā€™t quite get the idea that <ā€¦> is a _hard syntax_ to read. Two possibilities here:

  1. You think, as I do, that itā€™s not hard to read, even with several bounds.
  2. You think itā€™s hard to read: then maybe the solution would be to put them on a where clause?

Now compare with the pre-RFC linked above. It would be introducing a _tag_ (in the sense of the type-system) directly into the position of function arguments, i.e. variable bindings for the body of a function.

In other terms: itā€™s very confusing and, as several people stated for impl Trait in arg position, itā€™s not needed. Please donā€™t make the same mistake twice to add a feature that is not worth it. Especially if you ā€œpretend theyā€™re arguments.ā€ Rust would be going in the wrong direction here. The syntax should be ligther, yes, but not more confusing. Please stop.

@phaazon
I personally fully agree with the linked proposal that allowing constants in argument position will be a really good ergonomic boost. It's not only about function signatures (arguably where clauses do not "solve" the problem at all, but will make it harder to understand signatures, since you have to parse 3 places instead of just one), but also about how those functions will be used. Compare:

let r = _mm_blend_ps::<{2 * C}>(a, b);
let r = _mm_blend_ps(a, b, 2 * C);

The second line is much more natural in my opinion than the second one. Another example would be matrix creation: MatrixF32::new(3, 3) vs. MatrixF32::new::<3, 3>(). I strongly disagree that this functionality will be "very confusing". For user it's nothing more than an additional requirement for a function argument. We already can emulate const arguments via macros (see SIMD intrinsics inside std), but it's quite ugly and inefficient.

Regarding impl Trait in argument position, initially I was also against this feature, but after some time I've changed my mind and I believe it was a good decision in the end. The only incomplete part right now is interaction with explicit type parameters provide via turbofish. I think a good solution would be to make impl Trait arguments invisible for turbofish, i.e. users should use it when they are sure that explicit type disambiguation will not be needed. It would allow us to significantly reduce a need for _ inside turbofish in some scenarios.

I guess a discussion of const arguments is off topic here, so probably should not continue it here.

One thing that separates const arguments from impl Trait arguments (which @PvdBerg1998 mentioned) is that they are arguments, with all that that implies for type inference.

You can't pass a type itself as an argument, but you can pass a value, and letting a function infer its const generic arguments from its normal parenthesized arguments is completely reasonable.

This is fairly common in C++, for example:

template <typename T, size_t N>
size_t length(T (&array)[N]) {
    return N;
}

This is subtly different from simply accepting a const parameter and desugaring it to a const generic parameter (fn foo(const N: usize) -> fn foo<const N: usize>()). It's closer to constraining a parameter with a const generic parameter, and then inferring that const generic argument from the argument. So the matrix example above might look like this:

impl<const W: usize, const H: usize> MatrixF32<W, H> {
    fn new(w: usize<W>, h: usize<H>) -> Self { .. }
}

...where usize<X> is made-up syntax for "a usize with the value X." (Analogously, you might want usize<X..Y> for ranged types, which have been discussed in the context of generalizing NonZero.)

If you wanted to avoid roping in ranged types, you might look at dependent-typed languages which allow parameters to be used directly in types, with a bit of scope tweaking:

fn new(const W: usize, const H: usize) -> MatrixF32<W, H> { .. }

They are not arguments. They are generics. On a type system level, that means their _values_ live at compile-time, while a function argumentā€™s values live at runtime. Mixing both is extremely misleading.

As I care about type systems, this proposal goes against a lot of sane and sound principles. You donā€™t want to mix values and types. Because, yes, a N (note IĀ didnā€™t say usize, I said N), even though you think itā€™s a value, is way more akin to a type than a value. As a rationale, Haskell doesnā€™t have const generics but it has DataKinds, allowing to lift regular data constructors as types:

foo :: Integer -> Integer
foo 0 -- 0 has type Integer

-- but
data P (a :: Integer)

type MyP = P 10 -- 10 has kind Integer, which ā€œvalueā€ is the 10 type

So:

fn foo<const X:Ā usize>()

Here, X is more akin to a type than a value.

Also, I care about monomorphization. If I read this:

let x = foo(12, "Hello, world!", None);

With your proposal, if we look up at the definition of foo, itā€™s hard to know which arguments are going to monorphize foo. Because everytime you pass a different value for a const generic, you create a complete new function.

Maybe it feels more intuitive for _you_ and _your reasons_, but I also have reasons to say itā€™s non-intuitive at all in terms of type correctness. Stating that they are arguments is like stating that parametered functions, in math, have their parameters arguments. You are confusing parameters and arguments, demonstrating how bad that idea is.

You may have misread me, I specifically said "const arguments ... are arguments," not "const generics." My first Rust example is also exactly equivalent to DataKinds (usize<N> is the type-level version of N). There is no problem here in terms of type correctness- I'm not arguing in terms of intuition but by analogy to existing, established, and sound type systems.

As for your concern about monomorphization- that's no different than today! Every argument can potentially cause a new monomorphization. I would also add that the solution to this problem is to reduce the cost of all generics by sharing non-dependent code between instances, including-and-especially by converting const generics to runtime values when possible.

(And I'm utterly mystified by your claim that I'm confusing parameters and arguments, I was careful to distinguish them.)

I get what you mean but I still feel very concerned. Because the _argument_ is tagged _const_, it means you cannot pass it _a value_, runtime speaking. You need to pass it a _constant value_, which will map to the _const generic_. By the way, you didnā€™t mention it here but thatā€™s just an application of a _singleton type_ if you consider 10 as a type: its single possible value is 10.

As Iā€™ve been against impl Trait in arg position, Iā€™m against that one too (and youā€™ll get why we can compare here). Several points:

  1. I donā€™t think itā€™s necessary while _const generics_ add a lot of values.
  2. Since they are akin to types, they should belong where types are located, not where variables are located.
  3. I would really like to know why some people think <ā€¦> is harder than (ā€¦), especially considering struct vs fn. Currently, struct has only <ā€¦> and fn has both, because they both can be set parameters on.
  4. What I really disliked with impl Trait in arg position was the fact itā€™s harder to see what a function is really set parameters on with. When you read foo<A, B>, you know there are two parameters that will yield to a function definition and implementation.

I feel what you want to do here. foo(1, 3) feels better for you than foo<3>(1), but not for me. The reason for this is that foo(1, 3) should accept the second argument to be runtime-based and the proposal you give forbids it. I can see applications, though, especially with arrays, but I currently donā€™t like what it looks like. I would be much more for a solution that says ā€œif an argument is const, we can infer a type variable / const generic.

fn foo<const N: usize>(a: usize, b: usize | N);

Thatā€™s just imaginary syntax I came up with to explain my point: if you pass b as a const value, N = b and you end up with your nice syntax:

foo(1, 3)

If b is not a const value, itā€™s required that you pass N explicitly:

foo<3>(1, x)

Also, what would be the syntax for this, with your proposal:

fn foo<const N: usize>(x: [f32; N]);
fn new(const W: usize, const H: usize) -> MatrixF32<W, H> { .. }

It leads us to question about associated items, in short new() is asociated function, and must be asociated with concrete type, const generic items have semantic like "This type having this param...", i.e. dependent type. So, new() in the context shouldn't have any arguments. Explict usage should look like Type<32>::new(). And of course all const generics arguments should be elidable.

I feel what you want to do here. foo(1, 3) feels better for you than foo<3>(1), but not for me. The reason for this is that foo(1, 3) should accept the second argument to be runtime-based and the proposal you give forbids it.

That is not my motivation nor intention- I'm not talking about what "feels better" and I don't believe the compile/run-time distinction needs to be conflated with the type/value-level distinction. The two are already separate- const items (value or fn) are certainly not types!

All I'm getting at is the semantic connection that type inference draws between value-level parameters and type-level parameters. There is no relation to impl Trait- my point, again, was to distinguish const arguments from impl Trait, precisely to head off this sort of unproductive angst.

Also, what would be the syntax for this, with your proposal:

fn foo<const N: usize>(x: [f32; N]);

That syntax wouldn't change! I'm not proposing we remove const parameters from the type level (syntactically or semantically), just that we add them to the value level, partially to aid inference of the type-level ones.

At cppcon there was a talk about constexpr function parameters, which seems similar to what @PvdBerg1998 is talking about and seems just like the internals thread that @mark-i-m linked to. It seems to be well received there.

It seems like a good idea, but it should be worked out after we get the initial implementation for const-generics done

CppCon 2019: David Stone - Removing Metaprogramming From C++, Part 1 of N: constexpr Function Params

Edit: This was mentioned in that thread, here is the related paper for constexpr function parameters
https://github.com/davidstone/isocpp/blob/master/constexpr-parameters.md

I would like to request that we reserve the discussion of such a significant syntactic change for either a thread on internals or an issue (or amending PR) on the RFCs repo. This issue's comment section is already quite long and the syntax in question seems unnecessary for a usable MVP of const generics.

If we could move comments we probably would. We might want to lock the issue to @rust-lang team members and hide all the offtopic comments?

All design discussion should happen on the RFC repo and all bug reports should be in separate issues.

cc @rust-lang/moderation Is there precedent for doing this?

You can _convert_ a comment to an issue, but not move comments. Locking is fine, and i'd just hide off topic comments.

With the start of the new year, I thought it would be good to give another update on where we are now with const generics. It's been a little over 2 years since the const generics RFC was accepted. In the first year (roughly 2018), a series of large refactoring efforts were undertaken to facilitate const generics by improving the handling of generic parameters generally throughout the compiler. In the second year (roughly 2019), work began on implementing const generics itself: first by getting the bare minimum working, and then by slowing improving the integrity of the feature. People also started to experiment with using const generics in the compiler itself. More detailed descriptions of these efforts are in the first two update posts: [1], [2].


There's been good progress in the last couple of months since the last update.

  • @skinny121:

    • Fixed an issue breaking const generics under incremental compilation and across crates (https://github.com/rust-lang/rust/pull/65652)

    • Fixed a bug with type inference affecting const generics in diagnostics (https://github.com/rust-lang/rust/pull/65579)

    • Refactored the interface for const evaluation (https://github.com/rust-lang/rust/pull/66877)

  • @yodaldevoid implemented disambiguation of type and const parameters so that const parameters no longer need to be wrapped in {} (https://github.com/rust-lang/rust/pull/66104)
  • @eddyb fixed the use of const generics as array lengths, once lazy normalisation is implemented (https://github.com/rust-lang/rust/pull/66883)
  • @varkor:

    • Implemented structural_match checking to forbid types with custom equality checking from being used as const generics (https://github.com/rust-lang/rust/pull/65627)

    • Corrected some diagnostics to take const generics into account (https://github.com/rust-lang/rust/pull/65614)

    • Performed various refactoring and type inference fixing (https://github.com/rust-lang/rust/pull/65643, https://github.com/rust-lang/rust/pull/65660, https://github.com/rust-lang/rust/pull/65696)

A big thanks to everyone who has been helping out with const generics!


What's next? The biggest blocker for const generics at the moment is lazy normalisation, which is required for certain kinds of const generic bounds (such as in arrays). @skinny121 is currently investigating lazy normalisation, continuing their fantastic efforts taking down the big bugs in const generics. This would address many of the present issues with const generics.

Lazy normalisation aside, there are still a sizeable number of bugs and papercuts we'd like to address. If you're interested in tackling any of the remaining issues, feel free to ping me on the issue, or on Discord or Zulip for pointers on getting started. I'm confident we can make good headway in 2020 and hopefully approach a point where stabilisation becomes a viable discussion!

Is there a subset of const generics that doesn't hit lazy normalization and doesn't have many bugs and papercuts, but which can express a good amount of useful code? I notice that std's impls of many traits for arrays seem to work just fine. Maybe there's a narrowing that would allow other crates to write the kind of impls we have in std for their own traits, even though they don't support all the fancier functionality?

We're now more than halfway through the year, so I'll briefly summarise the work that has been going on. There have been lots of people involved in improving const generics support in the last 6 months, so thank you to everyone who's helped in some way! I especially want to thank @skinny121 and @lcnr, who have both tackled some large features that const generics has been lacking for a while: lazy normalisation for constants and const argument support in method calls, as well as fixing numerous difficult bugs. Their efforts are evident from the summary below.

Const generics is now being used in several places throughout the compiler, and there's already experimentation with traits and functions making use of const generics, e.g. slice::array_chunks and IntoIterator and FromIterator for [T; N] (https://github.com/rust-lang/rust/pull/65819, https://github.com/rust-lang/rust/pull/69985). The length 32 restriction for array trait implementations is also finally being prepped for removal.

We're currently discussing stabilising a subset of const generics that should cover a wide range of use cases (though there are still some issues left to address before const generics can be fully stabilised).


  • @skinny121:

    • Implemented the first version of lazy normalisation (https://github.com/rust-lang/rust/pull/68505, https://github.com/rust-lang/rust/pull/69181, https://github.com/rust-lang/rust/pull/67717, https://github.com/rust-lang/rust/pull/67890)

    • Improved performance (https://github.com/rust-lang/rust/pull/68118)

    • Fixed various errors (https://github.com/rust-lang/rust/pull/68143)

  • @lcnr:

    • Added support for explicit const generics in type arguments for methods (https://github.com/rust-lang/rust/pull/74113)

    • Completed the first implementation of lazy normalisation for constants (https://github.com/rust-lang/rust/pull/71973) with @skinny121

    • Added a lint for unused_braces (https://github.com/rust-lang/rust/pull/70081)

    • Fixed issue with const generics in patterns (https://github.com/rust-lang/rust/pull/70562)

    • Forbade dyn Trait for const generic parameters (https://github.com/rust-lang/rust/pull/71038)

    • Improved pretty-printing (https://github.com/rust-lang/rust/pull/72052)

    • Fixed various errors (https://github.com/rust-lang/rust/pull/70032, https://github.com/rust-lang/rust/pull/70107, https://github.com/rust-lang/rust/pull/70223, https://github.com/rust-lang/rust/pull/70249, https://github.com/rust-lang/rust/pull/70284, https://github.com/rust-lang/rust/pull/70319, https://github.com/rust-lang/rust/pull/70541, https://github.com/rust-lang/rust/pull/72066, https://github.com/rust-lang/rust/pull/74159)

    • Refactoring (https://github.com/rust-lang/rust/pull/70478)

    • Added tests (https://github.com/rust-lang/rust/pull/74392, https://github.com/rust-lang/rust/pull/74445)

  • @eddyb:

    • Fixed issues with const generics in array lengths (https://github.com/rust-lang/rust/pull/70452)

    • Improved pretty-printing (https://github.com/rust-lang/rust/pull/71232)

    • Improved error handling (https://github.com/rust-lang/rust/pull/71049)

    • Refactoring (https://github.com/rust-lang/rust/pull/70164)

  • @Aaron1011: fixed error with cross-crate const generics usage (https://github.com/rust-lang/rust/pull/72600)
  • @petrochenkov: fixed issues with name resolution and const generics (https://github.com/rust-lang/rust/pull/70006)
  • @yodaldevoid: fixed an issue with lifetime use in const generic parameters (https://github.com/rust-lang/rust/pull/74051)
  • @contrun: fixed an ICE (https://github.com/rust-lang/rust/pull/69859)
  • @oli-obk: refactoring (https://github.com/rust-lang/rust/pull/69981)
  • @Centril: improved diagnostics (https://github.com/rust-lang/rust/pull/70261)
  • @DutchGhost: added a test (https://github.com/rust-lang/rust/pull/70539)
  • @varkor:

    • Improved pretty-printing (https://github.com/rust-lang/rust/pull/67909, https://github.com/rust-lang/rust/pull/68111)

    • Fixed various errors (https://github.com/rust-lang/rust/pull/67906, https://github.com/rust-lang/rust/pull/68388, https://github.com/rust-lang/rust/pull/68434)

    • Improved diagnostics (https://github.com/rust-lang/rust/pull/70845)

    • Added tests (https://github.com/rust-lang/rust/pull/68312, https://github.com/rust-lang/rust/pull/70939)


While const generics has already come a long way, there are still bugs and sharp edges. If you're interested in tackling any of the remaining issues, feel free to ping me, @lcnr or @eddyb on the issue, or on Zulip, for pointers on getting started. Thank you!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

dnsl48 picture dnsl48  Ā·  3Comments

Robbepop picture Robbepop  Ā·  3Comments

pedrohjordao picture pedrohjordao  Ā·  3Comments

drewcrawford picture drewcrawford  Ā·  3Comments

dwrensha picture dwrensha  Ā·  3Comments