Avoiding awkward bound - nightly / generic_const_exprs

I'm using a trait with const usize values to select common vector/matrix array sizes:

trait Sizes { const A: usize; const B: usize; }

Making use of this requires nightly with #![feature(generic_const_exprs)], and starts introducing an awkward bound to carry around when defining any struct that uses it:

struct NewTypeA<S: Sizes>([f64; S::A])
where
    [(); S::A]: ,
;

I tried attaching the ZST array bound to the Sizes trait but that ends up giving an odd circular issue:

trait Sizes
where
    [(); Self::A]: ,
    [(); Self::B]: ,
{ const A: usize, const B: usize }

While I can certainly just add the weird bound everywhere (through all of the wrapping new types and all final structs that make use of those), it would be nice not to have to. Any ideas?

[playground link]

I don't think so. There's a reason the feature is not only unstable but marked as "incomplete". The [(); X]: bound is a placeholder making it possible to even experiment with generic const exprs, and is currently the only way to ensure the well-formedness of GCEs – most importantly, that the expression cannot panic (eg. due to overflow) in any permitted instantiation of the generic type. First off, it would lead to post-monomorphization errors, which Rust wants to avoid, and secondly, it's a difficult problem to rewrite the trait solver and type checker to even be able to reason about "types that could panic!"

An eventual stable implementation of GCEs will presumably include a less weird syntax for writing the required bounds, or possibly even allow the compiler to infer the bounds itself.

5 Likes

Ah, dang, that is about what I expected. But it's also frustrating because the sizes will be part of a sealed trait that I can guarantee will never have values higher than 6 (less than u8, but I'm using usize so it's native to arrays). Though I get that the compiler can't guarantee I won't accidentally try to implement it with usize::MAX + 1. I also assume that storing them as u8 and reading them as usize isn't enough, even though it would technically cap their value at 255?

I recall before const generics were stabilized there was something along the lines of 'const evaluatable checked' where I thought I saw numerical GE/LE assertion syntax in where clauses (e.g. N <= 6). But much of the stuff around nightly const generics is hard to find now. And I'm not even sure that could anchor the size guarantee to a single struct or trait, rather than having it spread to every single struct which contains it.

If not, it's just several dozen extra lines, oh well.

Why do you call it weird? It's perfectly legitimate syntax on stable Rust and can not be removed. This is valid stable Rust:

pub struct NewTypeA<const A: usize>([f64; A])
where
    [(); A]: ,
;

Automatic inference would be nice, though.

To my mind it's weird because it includes a minimal null array type in the where clause that may or may not have anything to do with the actual inner type (besides the constant itself):

struct NewTypeA<T, S: Sizes>(nalgebra::SVector<T, {S::A}>)
where
    [(); S::A]: ,  // All this just to guarantee the S::A, but no bounds
;

And it doesn't seem to matter whether the contained type is Sized or not, or even require that bound.

If only I could somewhere, in just one place, say: "Hey Rust, Sized::A is less than 7. Please fail to compile for anything beyond that."

For this to be useful this issue, which is old enough to go to school, needs to be fixed, first.

It's a bit pointless to invent convenient syntax for where clause if it's not auto-propagated, anyway.

And “weird syntax” (which already exists as stable syntax, anyway) allows one to experiment with such traits. Enough for a nightly feature IMO.

I know. What I meant was that it’s a really weird and unintuitive way of asserting "A is a valid array size".[1] It’s an even weirder way of asserting something like "A + B won’t overflow" because that one doesn’t even have anything to do with arrays! where [(); {A + B}]: is just a huge hack taking advantage of the fact that the array type constructor has uniquely been generic over an integer constant since day one and thus has special support in the compiler.


  1. Besides, all usize values are valid sizes of a ZST array anyway, so what gives? :face_with_raised_eyebrow: ↩︎

Trait aliases actually usually do elaborate where bounds not on Self, so I'm kind of surprised that they don't work to deduplicate the associated const expr bounds as well.

Given that you're using a type argument anyway, using typenum/generic-array instead of nightly const generic functionality is probably your best bet.

1 Like

What's actually being asserted in this case is that evaluation of the constant doesn't panic. Without lifting this requirement like this, panicking would cause a (post) monomorphization error.

1 Like

Ah, right, so it’s the same as my {A+B} example, just that you need to assert the non-panicness even when all you have is {A} :smile:

I was a bit confused because I just read somewhere that the actual max array size is isize::MAX (but was that bytes or elements?) because of LLVM and getElementPtr, so I presumed you have to have that bound if you want to create associated-const-sized arrays.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.