I don't understand why const generics arithmetic is so hard to implement as a language feature, and I don't think I ever will. I couldn't begin to understand the intricacies of the Rust compiler (what even is this???). I have no doubt that it's either an incredibly difficult or incredibly tedious problem to solve -- Rust developers/contributors have nothing but respect from me (especially with the insanely cool things you can do like test private functions and really testing in general, not to mention the million other things that Rust makes incredibly painless in comparison to C++).
That said, could macros somehow come to the rescue? I would really like to be able to do the following:
fn bean_doubler<N: usize>(beans: [i32; N]) -> [i32; 2 * N] {
let mut doubled = [0; 2 * N];
for i in 0..N {
doubled[2 * i] = beans[i];
doubled[2 * i + 1] = beans[i];
}
return doubled;
}
I guess that I could use Vec everywhere I was trying to use arrays, but the loss of size specificity/semantics seems like a real bummer to me. As a spaceflight simulation developer, it's really nice to see the exact size of your contiguous types and relate them through expressions.
All types of responses are appreciated! I'm really interested in const generics arithmetic and would love to learn about whether or not anyone else cares and/or why it's so hard.
The short explanation is probably just is that const generics are an afterthought, or retrofitted, if you will, into an existing type system. So it's unsurprising that true and actual types may still have benefits of better fitting into the type system they were designed for.
This is not to say there's any fundamental reasons against the true const generics supporting the same kind of use-case, it's just more ongoing design and implementation work for the compiler. With the fake ones that are truly types, the operations then are truly traits, and the constraints can be ordinary trait bounds. With const generics that aren't true types, operations on const generics don't have corresponding traits, and alternative mechanisms are required that aren't fully available (on stable Rust) yet.
This blog post may offer some insights into why const generics are complex. A sound type system must account for various possible corner cases. For example, what should language do with your example if 2*N is bigger than usize::MAX? Multiplication via * is just a sugar for std::ops::Mul, what should language do if its implementation panics (like on overflow)? Allowing widespread monomorphization errors is not a pleasant can of worms to open. Should compiler be able to derive that 2 * N is equal N * 2? What about N + N? It looks trivial on the first glance, but presents quite a rabbit hole if you start digging into it.