Closed withoutboats closed 2 years ago
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.
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:
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.
@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
type 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
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}
.