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:
Remaining implementation issues:
FIXME(const_generics)
comments.FIXME(const_generics:defaults)
).has_infer_types
.{X * 2}
.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):
const
parameters alongside type onesparse_generic_params
- parse const IDENT: Type
DefId
collectorty::Generics
- add const
parameters alongside type onesgenerics_of
- create ty::ConstParameterDef
from hir::ConstParam
Def
- add a variant for const
parameterswith_type_parameter_rib
- support both type and const
genericsConstVal
- add Param
variant akin to ty::TyParam
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 checkedConstVal
- add InferVar
variant akin to ty::ReVar
/ ty::TyInfer(TyVar(_))
InferCtxt
- const
counterparts to int_unification_table
and its UnifyKey
implty::relate::{Relate,TypeRelation}
- support relating ty::Const
, and handle ConstVal::InferVar
in a similar way to how super_combine_tys
does it for typesNote 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? :-)
@alexreg https://github.com/rust-lang/rust/issues/51192#issuecomment-394126083
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:
N+1 == N+1
should work out of the box)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.false
, either there could be a compilation error ("equations are axioms") or the trait cannot be instantiated ("equations are constraints")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.
/// 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
}
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.
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 oncetype ArrayWithoutLast<T, const N: usize> = [T; N - 1];
)N
ArrayWithoutLast
where
clause" e.g. [T; N - 1]: Sized
where [T; N - 1]:
(does that get ignored today? ouch!)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
).
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 theN < 128
expression won't make it work, initially)
You could usetrait Lt128<const N: usize> = IsTrue<{N < 128}>;
, I guess.
There's also the option of just writingwhere [(); 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
(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?
Next step is still https://github.com/rust-lang/rust/pull/53645 AFAIK.
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:
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?).
_[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
@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 FIXME
s 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.
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 FIXME
s
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 FIXME
s 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.
@yodaldevoid Filed as https://github.com/rust-lang/rust/issues/61537 .
@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();
}
You snippet doesn't crash on playpen: https://play.rust-lang.org/?version=nightly&mode=release&edition=2018&gist=d3fda06d2e8b3eb739afa99d5da84a33
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:
const
parameter of arbitrary type?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
fromN + 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,
@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:
pub type Alias
pub struct Struct
- crate B
extern crate crate_a;
use crate_a::Alias;
pub fn inner_fn(v: Alias
```
crash-log (44580).txt
@fzzr- sounds like #64730
Also sounds like https://github.com/rust-lang/rust/issues/61624
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.
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:
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:
<ā¦>
is harder than (ā¦)
, especially considering struct
vs fn
. Currently, struct
has only <ā¦>
and fn
has both, because they both can be set parameters on.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 likeType<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.
{}
(https://github.com/rust-lang/rust/pull/66104)structural_match
checking to forbid types with custom equality checking from being used as const generics (https://github.com/rust-lang/rust/pull/65627)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).
unused_braces
(https://github.com/rust-lang/rust/pull/70081)dyn Trait
for const generic parameters (https://github.com/rust-lang/rust/pull/71038)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!
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
FIXME
s 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.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!