Open newpavlov opened 5 years ago
Potentially related to https://github.com/rust-lang/rust/issues/43408.
The error now is:
error: constant expression depends on a generic parameter
--> src/lib.rs:5:17
|
5 | fn foo() -> [u8; Self::N];
| ^^^^^^^^^^^^^
|
= note: this may fail depending on what value the parameter takes
The error with min_const_generics
enabled is:
error: generic parameters must not be used inside of non trivial constant values
--> src/lib.rs:5:22
|
5 | fn foo() -> [u8; Self::N];
| ^^^^^^^ non-trivial anonymous constants must not depend on the parameter `Self`
|
= note: type parameters are currently not permitted in anonymous constants
which can definitely be improved.
I think we ought to prioritise good diagnostics for min_const_generics
from the beginning, so I'm making this issue as a blocker too.
const-generics-bad-diagnostics
(and const-generics-blocking
) be removed?Yes, thanks.
I think the following problem is related to this issue. If not, I apologize in advance. I don't quite understand why this code works:
#![feature(min_const_generics)]
struct WithArray<T, const SIZE: usize> {
data: [T; SIZE]
}
while this other one doesn't:
trait WithAConstant {
const SIZE: usize;
}
struct WithArray<T, U: WithAConstant> {
data: [T; U::SIZE]
}
This last one produces the following error:
error: generic parameters may not be used in const operations
--> src/main.rs:13:27
|
13 | data: [T; U::SIZE]
| ^^^^^^^ cannot perform const operation using `U`
|
= note: type parameters may not be used in const expressions
Thanks!
Hi there,
Is there a fix to this issue on the stable branch of Rust? This issue happens with rustc 1.52.1 (9bc8c42bb 2021-05-09)
.
Thanks
@parraman: sorry, I missed your comment. Your example should eventually work; it is a current implementation limitation. If you enable #![feature(const_evaluatable_checked)]
, it almost works (and probably can be made to work with a little tweaking to ensure U
's variance is known):
#![feature(const_generics)]
#![feature(const_evaluatable_checked)]
trait WithAConstant {
const SIZE: usize;
}
struct WithArray<T, U: WithAConstant> where [(); U::SIZE]: {
data: [T; U::SIZE]
}
@ChristopherRabotin: this is a limitation of the current stable version of const generics that we hope to fix in a future version.
Okay thanks for the quick answer @varkor . If you were to bet, would you say this will be fix in the the next six months or more? I'm asking because I prefer keeping my crate working with rust stable, but I would be OK switching to nightly for a few months. Thanks
There are still a number of design and implementation questions that need to be resolved with const_evaluatable_checked
before we can consider stabilising it, so likely not in the next six months.
I encountered this limitation, and it makes const generics much harder to apply to my use case, where the behavior of a large chunk of code is fully customized by the trait. Customization normally includes associated types, and should also be able to include associated constants to be used in const generics. For example:
// n-dimensional point
struct Point<const NDims: usize>([f64; NDims]);
// configuration of various things, including number of dimensions
pub trait Config {
const NDims: usize;
// more config here...
}
// use Point in structs generic over C: Config
struct ColorSpace<C: Config> {
data: Vec<(Point<C::NDims>, u8)>,
}
I understand that there are issues to resolve with the more general const evaluation (e.g. panic handling), but couldn't it be possible to refer to trait associated constants as a special case? I.e. have the above compile, but not the more complex Point<{C::NDims * 2}>
or Point<{arbitrary_const_fn(C::NDims)}>
?
Currently the only fix is to make the trait generic over NDims
, but it's both conceptually wrong (the trait needs to choose NDims
that makes sense for the configuration) and makes the API harder to use because an additional const generic creeps in everywhere. If Config
needed to define multiple such constants, the problem would be that much worse.
Just chiming in to say I'm running into the same problem.
I have a bunch of configuration parameters to tune a custom b-tree. Passing in a trait object for configuration makes my code much cleaner here; if for no other reason so I don't need to copy-paste <E: EntryTraits, I: TreeIndex<E>, const INTERNAL_CHILDREN: usize, const INTERNAL_CHILDREN: usize>, ...>
in about 20 places throughout my code.
I'm running into the same problem as @hniksic. I'm running into the awkward problem that I can't use the const field members in my trait to size some internal arrays. Now that const generics have landed, I'd love some love on this!
It looks like the following code successfully compiles on nigthly:
#![feature(generic_const_exprs)]
trait Foo {
const N: usize;
fn foo() -> [u8; Self::N];
}
Should I close this issue in favor of #76560?
@newpavlov Please note that the example from the previous comment (that defines ColorSpace
type) deosn't compile on current nightly. The compiler requires changing Point<C::NDims>
to Point<{C::NDims}>
because it treats C::NDims
as a full-featured expression rather than a constant. But making that change still fails to compile, citing an "unconstrained generic constant" and suggesting a bound of where [(); {C::NDims}]:
. Adding that bound leads to further errors about "unused parameter [EDIT: as of Rust 1.58.0-nightly the version with the C
" which I'm not sure how to address.where [(); C::NDims]:
bound does compile, though the bound looks very strange and I don't really understand it.]
It would be nice to have this resolved even without the full support for expressions in generic parameters. But even if that's not possible, the feature doesn't appear to work just yet even with generic_const_exprs
, so the issue shouldn't be closed at this point.
Somewhat related to this issue that I just discovered today: as an alternative to associated constants, it's possible to parameterize a trait with a const generic today on stable Rust using only min_const_generics
:
trait Foo<const N: usize> {
fn do_x(&self) -> [u8; N];
}
struct Bar;
impl Foo<42> for Bar {
fn do_x(&self) -> [u8; 42] {
[0u8; 42]
}
}
It's not quite the same thing, but it might be good enough for many of these potential applications.
Adding that bound leads to further errors about "unused parameter
C
" which I'm not sure how to address.
The unused parameter error should disappear now that #89829 has been merged. (see also issue #80977)
I'm trying to write this on nightly with #![feature(generic_const_exprs)]
pub trait Foo {
const COUNT: usize;
const NAMES: [&'static str; Self::COUNT];
}
pub struct Bar<T: Foo> {
name_lookup: [resource::UniformLocation; T::COUNT]
}
But I cannot
error: unconstrained generic constant
--> src\test.rs:18:5
|
18 | const NAMES: [&'static str; Self::COUNT];
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: try adding a `where` bound using this expression: `where [(); Self::COUNT]:`
error: unconstrained generic constant
--> src\test.rs:22:18
|
22 | name_lookup: [resource::UniformLocation; T::COUNT],
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: try adding a `where` bound using this expression: `where [(); T::COUNT]:`
I'm trying to avoid writing pub trait Foo<const COUNT: usize>
because it results in a lot of boilerplate I need to propagate : (.
I'm trying to write this on nightly with
#![feature(generic_const_exprs)]
pub trait Foo { const COUNT: usize; const NAMES: [&'static str; Self::COUNT]; } pub struct Bar<T: Foo> { name_lookup: [resource::UniformLocation; T::COUNT] }
But I cannot
error: unconstrained generic constant --> src\test.rs:18:5 | 18 | const NAMES: [&'static str; Self::COUNT]; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = help: try adding a `where` bound using this expression: `where [(); Self::COUNT]:` error: unconstrained generic constant --> src\test.rs:22:18 | 22 | name_lookup: [resource::UniformLocation; T::COUNT], | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = help: try adding a `where` bound using this expression: `where [(); T::COUNT]:`
I'm trying to avoid writing
pub trait Foo<const COUNT: usize>
because it results in a lot of boilerplate I need to propagate : (.
I'm in this exact same scenario, I'm trying to avoid propagating the N
generic parameter everywhere in my codebase. Is this currently possible with generic_const_exprs
?
I have a similar problem:
trait VectorSpace
where
Self: ...,
Self: Into<[Self::Scalar, Self::D]> + From<[Self::Scalar, Self::D]>,
{
type Scalar;
const D: usize;
}
Wouldn't this be the next thing, that should be stabilized? Doesn't seem too complicated.
I believe this is related to this issue as well on nightly, but I'm running into the following issue with a more complicated piece of code I'm running on. However, I've worked out a simpler example here: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=f15f265d88a25cfa9fc8822cf3c5950e
The compiler can't figure out that the bound is already met even though all the values for it are known at compile time. I've run into similar issues with this in the past where I have arrays with defined const size but the compiler can't have them swap places, and it always wants me to add additional where clauses for those things...
#![no_std]
#![allow(incomplete_features)]
#![feature(generic_const_exprs)]
trait MathTrait {
const NDIM: usize;
fn calculate_math(&mut self, data: &mut [f64]);
}
struct Solver<'a, M>
where
M: MathTrait + Sized,
[f64; M::NDIM]: Sized,
{
pub x: [f64; M::NDIM],
problem: &'a mut M,
}
impl<'a, M> Solver<'a, M>
where
M: MathTrait + Sized,
[f64; M::NDIM]: Sized,
{
pub fn new(problem: &'a mut M ) -> Solver<'a, M> {
Solver::<'a, M> {
x: [0.0_f64; M::NDIM],
problem,
}
}
}
trait PL {
const NSIZE: usize;
}
struct PhysicsComp{}
impl PL for PhysicsComp {
const NSIZE: usize = 32;
}
struct Physics<P: PL> {
comp: P,
}
impl<P: PL> MathTrait for Physics<P>
where
[f64; P::NSIZE]: Sized,
{
const NDIM: usize = P::NSIZE;
fn calculate_math(&mut self, data: &mut [f64]) {
/* do math here or whatever */
}
}
struct DoStuff<P: PL> {
data: P,
}
impl<P: PL> DoStuff<P>
where
[f64; P::NSIZE]: Sized, // Bound that is equivalent to the error of [f64; M::NDIM]: Sized,
{
fn problem(&self) -> bool {
let mut phys = Physics {
comp: PhysicsComp {},
};
// This line fails quite hard
let mut solver = Solver::<Physics::<P>>::new(&mut phys);
true
}
}
fn main() {
let failure = DoStuff {
data: PhysicsComp {},
};
failure.problem();
}
error[[E0599]](https://doc.rust-lang.org/nightly/error-index.html#E0599): no function or associated item named `new` found for struct `Solver<'_, Physics<P>>` in the current scope
[--> src/main.rs:76:50
](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=f15f265d88a25cfa9fc8822cf3c5950e#) |
12 | / struct Solver<'a, M>
13 | | where
14 | | M: MathTrait + Sized,
15 | | [f64; M::NDIM]: Sized,
... |
18 | | problem: &'a mut M,
19 | | }
| |_- function or associated item `new` not found for this
...
76 | let mut solver = Solver::<Physics::<P>>::new(&mut phys);
| ^^^ function or associated item cannot be called on `Solver<'_, Physics<P>>` due to unsatisfied trait bounds
error: unconstrained generic constant
[--> src/main.rs:76:26
](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=f15f265d88a25cfa9fc8822cf3c5950e#) |
76 | let mut solver = Solver::<Physics::<P>>::new(&mut phys);
| ^^^^^^^^^^^^^^^^^^^^^^
|
= help: try adding a `where` bound using this expression: `where [(); M::NDIM]:`
note: required by a bound in `Solver`
[--> src/main.rs:15:11
](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=f15f265d88a25cfa9fc8822cf3c5950e#) |
12 | struct Solver<'a, M>
| ------ required by a bound in this
...
15 | [f64; M::NDIM]: Sized,
| ^^^^^^^ required by this bound in `Solver`
For more information about this error, try `rustc --explain E0599`.
If I understand, rustc cannot correctly identify some bound when using the associated constant internally to a default method? https://play.rust-lang.org/?version=nightly&mode=debug&edition=2015&gist=e1114345b12197fdb922ef1acb2e80ae
Similarly to burdges' playground, I'd also like to throw in the ugly syntax necessary for using bool associated constants. It requiring that bound, and especially having to cast it to a usize, is not intuitive.
I planned to use this feature similarly to my playground, have a trait with a ton of default implementations for a few types, while still being able to use const type parameters as feature/optimization flags for different internal algorithms, operating on those types.
#![feature(generic_const_exprs)]
fn _internal_impl<const FLAG: bool>() -> &'static str {
if FLAG {
"run for accuracy"
} else {
"run for speed"
}
}
trait Operation {
const FLAG: bool;
fn run() -> &'static str where [(); Self::FLAG as usize]: {
// default implementation
_internal_impl::<{ Self::FLAG }>()
}
}
Can someone explain to a mere mortal what is the purpose of
where [(); TRAIT::CONST_NAME] :
syntax? What does it achieve in particular? I've seen something about variance but it gets muddy from there.
Can someone explain to a mere mortal what is the purpose of
where [(); AssociatedType::CONST_NAME] :
My very naive understanding is that without the bound either (1) [T; TypeA::LEN]
and [T; TypeB::LEN]
would be considered the same in the type system even if TypeA::LEN != TypeB::LEN
(because associated consts behave differently from associated types) or (2) a [T; TypeA::LEN]
in two different places would not evaluate to the same type because it has no covariance to itself. I'm not sure which of these two are more accurate, but I believe that it's a result of [T; N]
being covariant to T
but having no relationship to N
- and I think that is what this syntax is constraining.
I don't think the syntax is intended to stay - at least I sure hope not, it's pretty unfriendly and doesn't at all hint what it's doing. I think it's just some existing syntax that happens to do the trick. If it needs to be expressed, then some std::ops
type traits would imho be a better choice:
struct Foo<T, U: WithAConstant>
// Only one of these options
where AnyArray<U::Size>: Covariant
where AnyArray<U::Size>: CovariantOn<U::SIZE>
{
data: [T; U::SIZE]
}
But I think the compiler should be able to infer the correct usage and just error if something is too complex for a simple representation. At least I hope that's possible, because any sort of bound is unneeded noise for something that "should work" in the default case.
If inference isn't possible, I'd be kind of curious to see some examples explaining why. Or at least a better explanation from somebody about what the actual variance issue is
Well this makes the purpose clear. As you suggested, this will likely go away eventually, since for more "normal" cases the conversion from [T; N] to [T;3] when N=3 happens automatically without messy syntax.
The bigger question that is still bugging me is how someone came up with that construct and what does it truly mean...
It doesn't seem like anybody came up with the syntax specifically for this feature, I just think it's something that exists in the type system already. For example, on stable this compiles (even though it doesn't do anything):
struct Foo where [(); 100]: {}
And it seems like you can use any type, ()
just sort of hints "can be any type" even though that's not exactly what it says. This compiles on nightly:
#![feature(generic_const_exprs)]
trait WithAConstant {
const SIZE: usize;
}
struct WithArray<T, U: WithAConstant>
where [String; U::SIZE]: // using any type here has the same result
{
data: [T; U::SIZE]
}
Since the syntax already exists, I don't think anything syntax-related would at all block stabilization of generic_const_exprs
. I think it makes sense instead to create a separate feature like const_variance_inference
that has the purpose of eliminating its need, which could stabilize on its own time.
But I don't know if this has been discussed anywhere already, and would be curious to hear from anybody more involved in the RFC / decision making of this feature.
As far as status of stabilizing generic_const_exprs
It seems like there are quite a few issues open that be blocking https://github.com/rust-lang/project-const-generics/issues?q=is%3Aissue+is%3Aopen+label%3AA-generic-exprs
(Not sure if rustbot will let me do this since it's not my issue, but seems like this one belongs in that group too since it's related)
@rustbot label +A-generic-exprs
(edit: nope 😔)
The stupid solution that I found
trait Trait<const SIZE: usize> {
const SIZE: usize = SIZE;
fn fun() -> [u8; SIZE];
}
@igorechek06 It's great that you found a workaround that works for you, but I need to point out that your solution doesn't help when you need the trait implementation to specify the value of the constant (SIZE
in your example). That's the situation in the description of this issue, as well as the one presented in my earlier comment.
as of today all examples raised in this issue can be made compiled with sufficient #![feature]
s and adding where [(); X::CONST]:
and splitting trait to avoid E0391.
OP — works as-is.
#![allow(incomplete_features, dead_code)]
#![feature(generic_const_exprs)]
trait Foo {
const N: usize;
fn foo() -> [u8; Self::N];
}
https://github.com/rust-lang/rust/issues/60551#issuecomment-882031857 — requires (1) using Point<{C::NDims}>
to tell the compiler it is a constant not a type (2) adding where [(); C::NDims]:
to avoid the "unconstrained generic constant" error (as noted in https://github.com/rust-lang/rust/issues/60551#issuecomment-917601657)
#![allow(incomplete_features, dead_code, non_upper_case_globals)]
#![feature(generic_const_exprs)]
struct Point<const NDims: usize>([f64; NDims]);
pub trait Config {
const NDims: usize;
}
struct ColorSpace<C: Config> where [(); C::NDims]: {
data: Vec<(Point<{C::NDims}>, u8)>,
}
https://github.com/rust-lang/rust/issues/60551#issuecomment-991440346 — requires #![feature(generic_const_items)]
to put that where
clause on const NAMES
.
#![allow(incomplete_features, dead_code)]
#![feature(generic_const_exprs, generic_const_items)]
pub trait Foo {
const COUNT: usize;
const NAMES: [&'static str; Self::COUNT] where [(); Self::COUNT]:;
}
pub struct Bar<T: Foo> where [(); T::COUNT]: {
name_lookup: [String; T::COUNT]
}
https://github.com/rust-lang/rust/issues/60551#issuecomment-1066699575 — original definition has a cycle.
error[E0391]: cycle detected when building an abstract representation for `VectorSpace::{constant#0}`
--> src/lib.rs:5:31
|
5 | Self: Into<[Self::Scalar; Self::D]> + From<[Self::Scalar; Self::D]>,
| ^^^^^^^
|
note: ...which requires building THIR for `VectorSpace::{constant#0}`...
--> src/lib.rs:5:31
|
5 | Self: Into<[Self::Scalar; Self::D]> + From<[Self::Scalar; Self::D]>,
| ^^^^^^^
note: ...which requires type-checking `VectorSpace::{constant#0}`...
--> src/lib.rs:5:31
|
5 | Self: Into<[Self::Scalar; Self::D]> + From<[Self::Scalar; Self::D]>,
| ^^^^^^^
= note: ...which again requires building an abstract representation for `VectorSpace::{constant#0}`, completing the cycle
This can be workedaround/fixed by splitting VectorSpace
into two traits. Needs someone more familiar to confirm if it is expected or not.
#![allow(incomplete_features, dead_code)]
#![feature(generic_const_exprs)]
trait VectorSpaceBase {
type Scalar;
const D: usize;
}
trait VectorSpace: VectorSpaceBase
+ Into<[Self::Scalar; Self::D]>
+ From<[Self::Scalar; Self::D]>
where
{}
https://github.com/rust-lang/rust/issues/60551#issuecomment-1073054442 — very complicated example. First this can be made compilable with some changes
@@ -25,11 +25,13 @@
}
}
-trait PL {
+// NOTE: Added `Clone` superbound, see the other NOTE below for reason.
+trait PL: Clone {
const NSIZE: usize;
}
+#[derive(Clone)]
struct PhysicsComp {}
impl PL for PhysicsComp {
const NSIZE: usize = 32;
@@ -40,8 +42,6 @@
}
impl<P: PL> MathTrait for Physics<P>
-where
- [f64; P::NSIZE]: Sized,
{
const NDIM: usize = P::NSIZE;
@@ -56,13 +56,14 @@
impl<P: PL> DoStuff<P>
where
- [f64; P::NSIZE]: Sized, // Bound that is equivalent to the error of [f64; M::NDIM]: Sized,
+ [f64; Physics::<P>::NDIM]: Sized,
{
fn problem(&self) -> bool {
let mut phys = Physics {
- comp: PhysicsComp {},
+ // NOTE: I've changed `PhysicsComp {}` here to `self.data.clone()`
+ // because the `solver` below expects a `Physics<P>` not `Physics<PhysicsComp>`.
+ comp: self.data.clone(),
};
- // This line fails quite hard
let mut solver = Solver::<Physics::<P>>::new(&mut phys);
true
}
{
const NDIM: usize = P::NSIZE;
fn calculate_math(&mut self, data: &mut [f64]) {
/* do math here or whatever */
}
}
struct DoStuff
where
{
fn problem(&self) -> bool {
let mut phys = Physics {
// NOTE: I've changed `PhysicsComp {}` here to `self.data.clone()`
// because the `solver` below expects a `Physics ` not `Physics
but this fails hard you actually try to implement Physics::<P>::calculate_math()
to do anything interesting that used Self::NDIM
in a type — you need to add back the [f64; Self::NDIM]: Sized
bound and that triggers E0391 (type dependency cycle) again. Again like Case #4 I can workaround it by splitting MathTrait
into two traits.
@@ -1,5 +1,8 @@
-trait MathTrait {
+trait MathTraitBase {
const NDIM: usize;
+}
+
+trait MathTrait: MathTraitBase {
fn calculate_math(&mut self, data: &mut [f64]);
}
@@ -41,12 +44,18 @@
comp: P,
}
-impl<P: PL> MathTrait for Physics<P>
+impl<P: PL> MathTraitBase for Physics<P>
{
const NDIM: usize = P::NSIZE;
+}
+
+impl<P: PL> MathTrait for Physics<P>
+where
+ [f64; Self::NDIM]: Sized,
+{
fn calculate_math(&mut self, data: &mut [f64]) {
- /* do math here or whatever */
+ let x = [1.0; Self::NDIM];
+ data.copy_from_slice(&x);
}
}
@@ -65,6 +74,7 @@
comp: self.data.clone(),
};
let mut solver = Solver::<Physics::<P>>::new(&mut phys);
+ solver.problem.calculate_math(&mut solver.x);
true
}
}
{
const NDIM: usize = P::NSIZE;
}
impl
where
{
fn calculate_math(&mut self, data: &mut [f64]) {
let x = [1.0; Self::NDIM];
data.copy_from_slice(&x);
}
}
struct DoStuff
where
{
fn problem(&self) -> bool {
let mut phys = Physics {
// NOTE: I've changed `PhysicsComp {}` here to `self.data.clone()`
// because the `solver` below expects a `Physics ` not `Physics
So I think the questions remain for this issue are:
where [(); Self::CONST]:
bounds?
Initially reported here.
The following code:
Playground
Results in a "no associated item named
N
found for typeSelf
in the current scope" compilation error on current Nightly.Note that associated constants in
impl
blocks work without any issues.cc @varkor @yodaldevoid