MVP of associated constants for const generics

I think it may be a good idea to have a MVP for allowing associated constants to be used in const generics, basically we'd introduce a attribute #[usable_in_const_generic] (to be bikesheded) where all associated constants decorated with that attribute are restricted such that they're usable in const generics:

pub trait MyTrait {
    const C: usize;

pub struct S<const C: usize>;

impl<const C: usize> S<C> {
    pub const C2: usize = C; // works since C can be used in a const generic

impl<const C: usize> MyTrait for S<C> {
    const C: usize = C; // works since C can be used in a const generic
    // const C: usize = C + 1; // doesn't work without `#![feature(generic_const_exprs)]`

pub struct S2<T: MyTrait>([u8; T::C]); // works, since C has #[usable_in_const_generic]
pub struct S3<T: MyTrait>([u8; T::C2]); // works, since C2 also has #[usable_in_const_generic]

the idea is that #[usable_in_const_generic] would be stabilized to allow much more ergonomic use of const generics with associated consts rather than having to use an associated type C = Const<42> instead of const C: usize = 42.

If at some later point const generics no longer need to be restricted (which seems doubtful considering the lack of even theoretical progress on generic_const_exprs in years), then usable_in_const_generic would become a no-op and be (soft-?)deprecated and removed from the next edition.


What problem is this attribute actually solving? Associated consts as part of a min generic const exprs is something I've seen talked about my the people working on GCE, but I don't quite understand why this attribute would help? Either the feature works or it doesn't, and if it works then there's no reason to restrict it to only some consts, and if it doesn't work then allowing it for some consts doesn't work either.

basically, associated consts on stable are currently allowed to do things that const generics can't, e.g.:

pub trait Tr {
    const C: usize;
    type T;
    type T2;
    const C2: usize;
    type T3;

struct S<const C: usize>;

impl<const C: usize> Tr for S<C> {
    const C: usize = C + 3; // we can do this on stable
    type T = S<{ C + 3 }>; // we *can't* do this
    type T2 = S<Self::C>; // we *also* can't do this
    const C2: usize = C;
    type T3 = S<Self::C2>; // proposed attribute lets us do this

S<{ C + 3 }> style of things are not allowed because the compiler can't figure out how to prove expressions are equal or not in general, which is needed to figure out if types are the same type or not, which would ruin the compiler's type system. This is what's gated by GCE.

The proposed #[usable_in_const_generic] attribute would restrict associated constants such that they can be used in const generics, basically by restricting those constants to the subset the compiler can prove are equal or not, thereby allowing those associated constants to be used by const generics.

1 Like

Is this subset documented somewhere?

It seems likely to be related to StructuralEq, those constants that are comparable enough for match exhaustiveness checking are likely comparable enough to work in type checking.

yes, though the documentation seems rather obtuse...

My proposal is that #[usable_in_const_generic] will exactly match const generics' restrictions, everything you can do with const generics, but no more, you can also do with #[usable_in_const_generic] associated constants.

This means that any future expressiveness gains that const generics get, #[usable_in_const_generic] will also gain in lock-step. So this is only related to StructuralEq (recently replaced with ConstParamTy and maybe StructuralPartialEq, idk all the details) in that when const generics work on more than just scalar integer-like types, then #[usable_in_const_generic] will too.

I know it doesn't really matter at this point, but a less clunky identifier for the attribute could be generic_compatible. Works pretty well because we already know the associated item is a const.

ok, I'm totally open to suggestions!