Hello, in the library I'm writing, basically all the functions and types I use have the same set of generics (both const and types). I wanted to put everything into a bag (let's call it Module
) so that it's easier to read both in the library code and user-side. Something like:
trait Module {
const ROW: usize;
const COL: usize;
type Foo: SomTrait;
//etc.
}
struct Bar<M: Module> {
baz: [[bool; M::COL]; M::ROW],
foo: M::Foo,
}
This, and bound checking (index > Self::ROW
), are basically the only usage I will have of this feature.
I already use a nightly compiler for this lib (to get the generators), so I don't mind adding another feature. The question is: is it advanced enough to handle my usecase?
Also, when trying this, I get some weird errors:
error: unconstrained generic constant
--> src/lib.rs:12:10
|
12 | baz: [[bool; M::COL]; M::ROW],
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: try adding a `where` bound using this expression: `where [(); M::COL]:`
error: unconstrained generic constant
--> src/lib.rs:12:10
|
12 | baz: [[bool; M::COL]; M::ROW],
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: try adding a `where` bound using this expression: `where [(); M::ROW]:`
I'm not sure about what that means.
Edit: it looks like I can get rid of the errors with a where [[bool; M::COL]; M::ROW]: Sized
bound (taken from this SO entry), but the main question remains.
BTW, I wonder if I can put this bound on the trait directly, somehow.
Disclaimer: I'm not involved in developing rustc.
I would say it's highly unstable, but for experimenting simple cases like this I guess it's fine. You still should expect bugs/crashes, and definitely not using this in serious project.
It just means M::COL
and M::ROW
can be evaluated without a panic. Since it's ok to write something like this:
trait Module {
const ROW: usize;
const COL: usize;
}
struct Foo;
impl Module for Foo {
const ROW: usize = todo!();
const COL: usize = todo!();
}
1 Like
Thanks for the answer!
My project is somewhat serious because I plan to publish it. I guess I'm stuck with my list of generics for now, then.
Your explanation about why I must put any random constraint implying the const generic parameter is really clear, thanks!
It does not make really sense IMHO, since if I attempt to use a panicking const, the code fails to compile anyway.
I wonder if adding this kind of bound is temporary, and if it is not, if I can add a constraint directly into the trait saying that it is forbidden to use something else than an usize
.
An (ugly) workaround might be something like this:
struct Dim<const ROWS:usize, const COLS:usize>;
trait Module {
type Dim;
type Foo: SomeTrait;
//etc.
}
struct Bar<M: Module<Dim=Dim<R,C>>, const R:usize, const C:usize> {
baz: [[bool; C]; R],
foo: M::Foo,
}
I have no idea about whether the bound could be omitted, but I guess it's hard to. Omitting the bound kind of making your struct have implicit bound, for example, if you wrote this instead:
baz: [bool; { M::COL * M::ROW }]
Then then bound would have to requirer M::COL * M::ROW
evaluates. Of course we can make this a somewhat non-local error that happens after monomorphization. So there's a chance. I don't really know.
I think it's kind of hard to add a constraint like that. Since you can do arbitrary computation with generic_const_exprs
, it's hard (read: impossible) to prove it's always well-formed.
1 Like
Hum, the idea was to shove all the generic parameters into one trait to have a single generic parameter to pass around (to mimic a bit the OCaml module system); so while your workaround is cool, it defeats to purpose of my Module
trait.
That's what I meant, that during the monomorphization, the compiler knows which one is well or ill-formed.
Anyway, I understand it's better to not use that in something else than an experiment, and since this lib is to be used on controllers, I better stay away from it.