How unstable is generic_const_exprs?

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.