Limitation of associated const in traits

TLDR:

Why does use_const fail but use_type works:

trait Length {
    const LEN: usize;
    type Type;
}

// Error: generic parameters may not be used in const operations
fn use_const<T: Length>(t: T) -> [u8; T::LEN] {
    todo!()
}

fn use_type<T: Length>(t: T) -> [T::Type; 4] {
    todo!()
}

Longer version

I have a trait Length which encodes the "length" by some metric of a type in an associated const LEN (i.e. if I have an instance of a T, I can store it in an array of [u8; T::LEN].

However, when I try to actually implement this function, I get the generic parameters may not be used in const expressions error.

In researching this error, I came across Can't use type-based constant in generic , but this seems to be more to do with scoping (perhaps monomorphization/lack thereof of consts inside fn bodies.

I've also tried something like this:

trait Length {
  const LEN: usize;
  fn get_buffer() -> [u8; Self::LEN] {
    [0; Self::LEN]
  }
}

but this gives the same error.

The bit that's confusing me is the difference between this behaviour and the behaviour of associated types. As someone who hasn't spent much time looking at the internals of const generics, superficially they seem very similar to regular type generics, so I was surprised when this didn't work.

Moreover, is this a limitation that is expected to be removed when const generics mature, or is there a fundamental reason why this behaviour isn't possible with const generics (but is possible with regular type generics)?

Also, if there's a simpler/more idiomatic way (or workaround) to get the same results I'd be very grateful to hear about it.

Thanks in advance :grin:

To get an idea on how to answer this question, let’s look at the error message on nightly:

(Playground) Errors:

   Compiling playground v0.0.1 (/playground)
error: generic parameters may not be used in const operations
 --> src/lib.rs:7:39
  |
7 | fn use_const<T: Length>(t: T) -> [u8; T::LEN] {
  |                                       ^^^^^^ cannot perform const operation using `T`
  |
  = note: type parameters may not be used in const expressions
  = help: use `#![feature(generic_const_exprs)]` to allow generic const expressions

error: could not compile `playground` due to previous error

Aha! use `#![feature(generic_const_exprs)]` to allow …, let’s try that!

#![feature(generic_const_exprs)]

trait Length {
    const LEN: usize;
    type Type;
}

// Error: generic parameters may not be used in const operations
fn use_const<T: Length>(t: T) -> [u8; T::LEN] {
    todo!()
}

fn use_type<T: Length>(t: T) -> [T::Type; 4] {
    todo!()
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
warning: the feature `generic_const_exprs` is incomplete and may not be safe to use and/or cause compiler crashes
 --> src/lib.rs:1:12
  |
1 | #![feature(generic_const_exprs)]
  |            ^^^^^^^^^^^^^^^^^^^
  |
  = note: `#[warn(incomplete_features)]` on by default
  = note: see issue #76560 <https://github.com/rust-lang/rust/issues/76560> for more information

[…]

Alright, so that compiles, though it warns about generic_const_exprs being “incomplete”.

What this indicates to us:

  • there already exists (experimental) compiler support, so the answer is most definitely “yes, the limitation is expected to be removed when const generics mature”
  • the experimental support is still really experimental, so you can’t be 100% certain that it really is going to work, and in particular work this way. I mean, that’s generally the case with unstable features, but in particular with “incomplete” ones.

Now my take, i.e. stuff that the compiler alone wouldn’t tell you: AFAIK, one of the main purposes of the generic_const_exprs feature is to allow code such as yours. Also, one of the reasons it’s currently disallowed is that it’s not quite clear how failure to evaluate T::LEN should be handled. (Const operations can panic at compile-time.) The generic_const_exprs feature approaches this problem by enforcing that any (generic) user of the use_const function will still need to feature the T::LEN somehow in its type signature to indicate “this constant’s value is important, and I will wail to compile if it fails to evaluate”. Of course, see also the linked issue #76560, and further links on that tracking issue to the proposal and design document, if you’re interested in more details.

9 Likes

Your code compiles when the unstable generic_const_exprs feature is enabled. The feature has not yet been added to stable Rust, since it still fails on several edge cases, but it is expected to eventually be stabilized. In general, constant evaluation has historically used different mechanisms in the compiler than trait resolution, so lots of work needs to go into unifying the two for full const generics.

2 Likes

My (very uninformed) opinion on is that this should just be a compile error? Are there times when you'd want to allow certain consts to panic in their evaluation?

Other than that, thanks for the answer, good to know it's something that will hopefully be usable on stable one day :grin: