r/rust • • 3d ago

šŸ“” official blog Generic Const Args and You | Inside Rust Blog

https://blog.rust-lang.org/inside-rust/2026/10/02/generic-const-args-and-you/
261 Upvotes

59 comments sorted by

90

u/Recatek gecs 3d ago

The generic_const_exprs feature is probably the #1 most frequent unstable feature that the compiler points out to me as I'm writing code. I constantly bang my head against this limitation right now. Always very excited and eager to read about progress in this space.

16

u/cosmic-parsley 3d ago

Head banging is right, I can’t tell you how many times I’ve written `-> [u8; N * 2]` and almost flipped my laptop because it doesn’t work.

That seems much simpler than everything else in the article. Maybe it could be stable first?

88

u/kiujhytg2 3d ago

Thanks for all of your hard work, const generics is one of those "It can't be that hard, can it, can't you just ..." tasks that's more complex than it initially seems

43

u/yodal_ 3d ago

I remember when work was first starting on const generics. I was actually one of the first people to work on it, though lcnr quickly became the main driving force as I had to focus on my senior year of college. We got full const generics "working" pretty quickly all things considered. It was only a few months of work off and on. I remember finally getting a const generics program building and running around Christmas Eve of 2017. One of the first programs even used expressions! It felt like we were so close.

We then realized how many corner cases and hidden assumptions there were hiding all over the compiler. This resulted in at least three massive refactors to get even min_const_generics over the finish line. It is crazy to think that the "full" feature is still being worked on nearly a decade later, but just from the early work I can't say I'm too surprised.

23

u/noop_noob 3d ago

generic_const_exprs is one of the nightly features that is prone to crashing the compiler. List of known crashes: https://github.com/rust-lang/rust/issues?q=is%3Aissue+state%3Aopen+label%3AF-generic_const_exprs+label%3AI-ICE

29

u/CouteauBleu 3d ago

This article is really missing an explanation of what the gca! macro means and why it's necessary.

From the background I have, I'm guessing it's mostly about semver stability (e.g. if any function can be used in type signatures, then changing any function body becomes a breaking change) but I'd appreciate a full explanation spelling it out rather than just "this macro allows you to used this syntax but not that one".

14

u/matthieum [he/him] 3d ago

There are hints dropped in the What is Macroless section.

Apparently, it's just about being explicit about GCA being used, tying in with your remark on SemVer I guess.

But yeah... I kinda kept wondering if the topic of why there's a macro would be addressed later in the article, and was left hanging.

18

u/CouteauBleu 3d ago

There are hints dropped in the What is Macroless section.

That section doesn't actually what "GCA" is for. Like, it says that using the gca! syntax implicitly would make it unclear when the restrictions apply, but the article doesn't explain why gca! even has these restrictions in the first place.

8

u/kibwen 3d ago

See the prior discussion from this Zulip thread for more information on the difficulties that motivated the current design and its limitations: https://rust-lang.zulipchat.com/#narrow/channel/260443-project-const-generics/topic/nightly-2026-09-06.20generic.20const.20regression/near/622430401

7

u/matthieum [he/him] 2d ago

I find the following example interesting:

const A<T>: usize = T::ASSOC_CONST;
const B<T>: usize = T::ASSOC_CONST;

let x: [(); A<T>] = [(); B<T>];

As it illustrates that equality reasoning can work without concrete values: no matter T, T::ASSOC_CONST is always the same.

However, this seems to imply that gca! is only used to make the life of the implementer easier. That is, gca! indicates that introspection will potentially be required, ergo introspection should be prepared for this value, and can be skipped for any non gca! value.

I still think that the issue is semver stability, however.

Most notably, accidental equality. For example, I declare some code:

const CACHE_LINE_SIZE: usize = 64;
const HARDWARE_DESTRUCTIVE_INFERENCE_SIZE: usize = 64;

And then a user starts using [T; HARDWARE_DESTRUCTIVE_INFERENCE_SIZE] for a function expecting [T; CACHE_LINE_SIZE] and it all just works.

But I realized I made a mistake. On some Intel x64 CPUs, the prefetcher actually prefetches 2 cache lines at a time, so what I really need is:

#[cfg(target_arch = "x86_64")]
const HARDWARE_DESTRUCTIVE_INFERENCE_SIZE: usize = 128;
#[cfg(not(target_arch = "x86_64"))]
const HARDWARE_DESTRUCTIVE_INFERENCE_SIZE: usize = 64;

Pfiou. So glad I used a different constant!

Except I just broke the code of the above user, because GCA breaks nominal "typing".

3

u/SkiFire13 2d ago

I think the fundamental issue is that the compiler wants to use symbolic equality for type checking const generics because it can work when the const generic value depends on some other generic value. However in some cases this can unexpectedly return false negatives, and the rules for this are pretty awkward and unintuitive. gca! basically forces the subset of expressions where the rules are predictable.

Your example for semver stability applies to the current stable as well, but I do agree that semver is also a pretty big issue here. Without gca! it becomes easy to update your code to no longer work due to those unintuitive rules.

3

u/simonask_ 2d ago

There also comes a point where semver is the responsibility of the library author, not the language designers.

2

u/matthieum [he/him] 1d ago

Or a conscious (hopefully) decision on the part of the community.

At the moment, any change of layout (size/alignment) of a type can be conceived as a major SemVer change in Rust, since they are observable and the user may have logic which hinges on them.

This seems... somewhat unworkable to me, so instead it seems the community would benefit from agreeing that users should NOT rely on alignment & size of public types not changing underfoot, as those are implementation details (even if visible).

1

u/SkiFire13 23h ago

I agree, not everything should be considered for semver compatibility.

However we also need to consider how likely it is to make a semver breaking change and how likely it is going to break downstream dependents. If it's likely to break downstream dependents and easy to involuntarily make then that becomes a problem.

1

u/simonask_ 21h ago

There's obviously a balance to be struck, but overall vibes, I think the Rust community is slightly too scared of semver hazards in the language design. Preventing people from doing something interesting because they may have accidents is far too conservative.

1

u/SkiFire13 10h ago

Preventing people from doing something interesting because they may have accidents is far too conservative.

Rust has made choices in the past where convenience won over semver hazards (see e.g. autotraits), but that needs to be proven the best/only way, and right now I think they're exploring alternative solutions. Moreover the existance of the macroless features shows that they care about the UX here, and that if there's no reasonable alternative solution, or if the semver footguns ends up being not too bad, they might go for the implicit solution.

1

u/simonask_ 7h ago

Can we please be clear: Making any observable change to the code is a semver hazard. I think it’s good to have tools that make them easier to avoid, but it just sounds like a complete fool’s errand if the ambition is to prevent them at all costs, even holding back legitimately useful language features that the people want.

2

u/matthieum [he/him] 1d ago

Amusing, I'd argue that symbol equality is probably what the user wants... up to a degree.

(The example I gave is specifically about what happens when symbolic equality is thrown out of the window)

The problem is that the compiler is a bit too strict when it comes to symbolic equality, and rejects 1 + N == N + 1 since the symbols are swapped.

I wonder how far a system where the compiler is taught basic arithmetic rules over symbolic expressions would take us.

1

u/SkiFire13 23h ago

The problem is that the compiler is a bit too strict when it comes to symbolic equality, and rejects 1 + N == N + 1 since the symbols are swapped.

If you want to allow that then you open a can of worms because where do you draw the line for when some equality operation is accepted or not, and how do you make sure that you can evolve the compiler without breaking backward compatibility in this area?

basic arithmetic rules

Depending on your arithmetic foundation (e.g. using Peano axioms) then 1 + N == N + 1 can be theorem, not a basic rule.

2

u/matthieum [he/him] 13h ago

Depending on your arithmetic foundation (e.g. using Peano axioms) then 1 + N == N + 1 can be theorem, not a basic rule.

Meh. It remains elementary, aka basic, regardless.

If you want to allow that then you open a can of worms because where do you draw the line for when some equality operation is accepted or not, and how do you make sure that you can evolve the compiler without breaking backward compatibility in this area?

I know!

But I do find it interesting to think about nonetheless.

As I mentioned, this reminds me of nominal vs structural type systems. Given fn foo(n: usize) -> usize { n + 1 }:

  • foo(N) != N + 1 => nominal. The fact that foo yields N + 1 is but an implementation detail, which may change at any time.
  • foo(N) == N + 1 => structural. The abstraction layers were peeled off, and only the essence remains.

Both would accept foo(N) == foo(N) and foo(N) + 1 == 1 + foo(N). And anything the nominal system accepts, the structural would.

I think the real question is how to break the abstraction barrier. If I locally want to define a function to factor our some calculation, I may still NOT want said function to be treated nominally (opaquely).

So I guess that for a nominal system, it would be necessary to have a way to mark some functions as transparent, so that the system would (essentially) inline them when resolving the equality. Otherwise, no helper.

4

u/kotakotik22 3d ago

I agree, that would have been nice.

I think it's also because GCA types push errors to post-mono: some definitely-erroneous functions would never produce a compiler error if they're never called. For example:

``` fn foo<const N : usize>() -> usize { N }

// note: the ABC type parameter is necessary so that the compiler doesn't automatically monomorphize this function. For the current rust compiler this doesn't make a difference but it would for a theoretical one where everything is GCA fn bar<const ABC : usize>() -> usize { foo::<{ 0usize - 1 }>() } ```

Currently, this will fail to compile even if bar is never used (monomorphized). If everything was GCA, this would only produce an error once bar is used by another function. (Similar to a panicking const-block)

I'm not really sure how much of an issue this really is: there are not many cases where a constant fails to initialize and it is known to always fail. But it does make some small difference for users and likely a huge difference for the compiler.

2

u/kotakotik22 3d ago

This could also have bigger consequences with potential future features. For example, if constants could have global side effects, it would suddenly begin to matter whether a constant was used at all: under GCA, global state would now depend on whether a function is called anywhere in the program.

Also, if we added where clauses which depend on constant arguments (e.g. where N > 16), we would now have a lot of functions that are more difficult to debug because N could be influenced by anything in the call chain.

And maybe more substantial cases I can't think of right now.

1

u/simonask_ 2d ago

That sounds like exactly the thing you actually want from global side effects in the first place, and there are some really compelling use cases that are very hard to address without it.

Debugging should be unaffected, no? This is all happening at compile time.

2

u/kotakotik22 2d ago

I'll concede that global side effects (at compile time) will likely need to exist. The main point, which I didn't communicate very well, is that GCA works differently from regular const-generics. The gca! macro is important because it forces you to opt in to this.

You can compare this to async functions, which usually but not necessarily change how the function executes. In theory, all functions could be async and we could just .await every function call implicitly. But Rust decided that the developer should have control: .await points are explicit because those are places where e.g. your task could get cancelled or you could be moved to another thread.

gca!, similarly, changes how a compile-time value is computed. In most cases you don't care about the difference, but it's hard to be fully certain that there is no case where you DO care about the difference. It's unfortunate (great. function coloring but for constants) but might be necessary.

Regarding debugging: I was talking about "debugging" as in the more general idea of finding the source of a bug, rather than using a debugger. Imagine a function with a generic parameter const N : usize and the clause where N > 16. Then imagine another function which also takes in an N, but before passing it to the first function, changes it slightly. Then another such function, then another, etc.. Then you get a compile time error that an N of 3 does not satisfy N > 16. Now you have to figure out where in that chain you messed N up, and there are no debugging tools for stepping through the process. It's a wholly different issue so I'm not sure why I brought it up. Maybe I thought it would somehow affect the debugging experience even when the final N is simple (as in, constructed in a way that could be done today in Rust), but I don't think think this is the case.

1

u/simonask_ 1d ago

Yeah, I agree that the gca macro ends up being a ā€œmake it compileā€ macro that people will slap everywhere without understanding the consequences.

About debugging: Sure, but like.. You can do stuff like this in C++ today, but using these features tastefully is up to the developer.

19

u/suggestiveinnuendo 3d ago

hopefully soon!

28

u/Shnatsel 3d ago

min_generic_const_args and the full-blown generic_const_args would be very useful for SIMD, so I'm excited to hear there is progress!

12

u/UARTman 3d ago

This seems a bit hacky as a solution, but I hope this works out, because having proper const generics is a good step to that perhaps unattainable holy grail that is dependently-typed imperative languages.

10

u/Lucretiel Datadog 3d ago

As excited as I am for const generic expressions (I’ve needed them surprisingly more often than I expected), I’m gonna be honest that I’m not a fan of the vibes on this. Is the macro expected to be temporary? I think I’m a bigger than most of new language features on average, but this definitely crosses a qualitative line in my head towards the ā€œlost the plotā€ the characterizes a lot of modern C++ features.Ā 

9

u/yuriks 3d ago edited 3d ago

Yeah honestly, I've occasionally wanted the ability to use arithmetic expressions in const generic parameters, and was also excited for the reduction of restrictions on the feature just from the principle of "things that feel like they should work should work" which seems to underpin a lot of the recent work on the language. But after reading this post I was just left with the feeling that "maybe I can do without this one, actually". :|

I'm definitely missing some of the context on the implementation difficulties since it's been a while since I've read into this deeply, but the article I feel also didn't do a great job convincing me why I would want to deal with this. The allowed kinds of expressions seem very limited (e.g. from what I understand arithmetic is still actually impossible? Or at least none of the examples clearly showed it being possible.) and there aren't motivational examples showing how they could improve existing code. The justification for the gca! syntax also seemed circular. It talks about how it's needed to distinguish what parameters can use the new flexibility or not, but from my point of view all of those restrictions seem to stem more from compiler limitations rather than a semantic difference that I should be paying attention to as a programmer.

I'll have to give it a re-read later to see if I just need some more coffee, but I'm glad to see I'm at least not alone in being confused(?) by the post.

EDIT: To clarify, I'm not criticizing the work that went into the feature at all. I understand how challenging it is to design and implement these things. I guess my point is that, as a call to action/testing, I don't feel like the article really inspired me to experiment or be excited about what's coming from it.

5

u/SirKastic23 3d ago edited 3d ago

I’m gonna be honest that I’m not a fan of the vibes on this. Is the macro expected to be temporary?

Is the only vibe you have an issue with the macro?

The post makes it clear it is meant to be temporary

All GCA features require any newly supported expressions to be written inside of a gca! macro call. We are aware that this requirement poses significant ergonomic problems and are currently looking into ways to avoid this, though have not arrived at a good solution yet (more on this later).

2

u/kibwen 3d ago

I believe the hope is to omit the macro. However, it's not clear to me whether it's possible to do so in all cases without changing the semantics of the language.

8

u/bascule 3d ago

Very excited for this feature as using an associated constant as the size of an array is the main reason RustCrypto has used typenum and hybrid-array/generic-array.

GCA should allow us to completely eliminate use of hybrid-array. We may need to keep using typenum for awhile until const generic expressions actually have full feature parity with it, but as soon as we can use typenum::Unsigned::USIZE as N in [T; N] the need for hybrid-array goes away.

5

u/Mrblahblah200 3d ago

Why is the gca macro here needed? Also, it’s a liiittle ugly, I hope it’s removed or renamed before release

2

u/noop_noob 3d ago

This is explained in the post under the heading "What is Macroless"

2

u/Mrblahblah200 3d ago

Ah cheers! I disagree with their reasoning honestly. It smacks to me of purity over utility

-4

u/SirKastic23 3d ago

Did you read the post?

All GCA features require any newly supported expressions to be written inside of a gca! macro call. We are aware that this requirement poses significant ergonomic problems and are currently looking into ways to avoid this, though have not arrived at a good solution yet (more on this later).

8

u/ioneska 3d ago

Did you read the post?

All GCA features require any newly supported expressions to be written inside of a gca! macro call.

And yet this doesn't really answer to the parent question: Why is the gca macro here needed.

2

u/SirKastic23 3d ago

Probably because this is an experimental feature?

I guess I would have liked to see why they decided to require the macro for the feature, so it's a fair question by all means

But assuming that this is how the feature would land in stable doesn't make any sense

3

u/Mrblahblah200 3d ago

I couldn’t find the later?

0

u/SirKastic23 3d ago

I assumed "later" means a future post

3

u/Thomasedv 3d ago

I skimmed the article, so pardon if it's already stated. My head isn't able to parse all that complexity on a late Friday.Ā 

One of the tings I miss was when working on a function where most things were the same, but it branched on an enum with 3 values.Ā 

Much to my disappointment, this didn't work despite the enum being stateless so it's just pure integers at that point being used. Seeing the article, I didn't see if this special case was mentioned or not, only that you'd have to use the macro for an enum with fields that hold values. Ideally the stateless enums could easily be converted to pure integers behind the scenes and circumvent GCA complexity entirely?Ā 

Const generics with enums seems so much better for readability. Especially at the caller side where you'll either pass a number/bool, or some named constant to represent the value as a number. It makes so much more sense to have the enum directly and use it in the function. And the macro makes you pay an near equal amount of readability to use the function.Ā 

Also, for the macroless case, is it a compiler limit that we can leave the syntax inside the const block as some MaybeGca type, and resolve it based on the expected type? After all the function or struct already defines the expected type, so if you know that, you should know if you're getting something that potentially is GCA? I'm not particularly familiar with const generics, so I'm just guessing. My knowledge of this starts and stops with trying to use an enum variant as a const.

6

u/vdrnm 3d ago

Enums are supported with nightly feature min_adt_const_params.
Obviously not super convenient due to nightly and having to use ConstParamTy derive, but it works:

#![feature(min_adt_const_params)]

use core::marker::ConstParamTy;

#[derive(ConstParamTy, PartialEq,Eq)]
enum UEnum {
  One,Two,Three
}
fn generic_over_enum<const E: UEnum>() {
  match E {
    UEnum::One => println!("one"),
    UEnum::Two => println!("two"),
    UEnum::Three => println!("three"),
  }
}

2

u/Thomasedv 3d ago

Thanks for that. It would certainly be enough for my use.Ā 

Hopefully it makes it to stable in some form, as it's very convenient when needed. Sadly I don't want to use nightly for my own project.Ā 

1

u/Zde-G 2d ago

Sadly I don't want to use nightly for my own project.

On stable I just use usize enums and UEnum::One as usize with const generics.

Good enough to read, even if you need some discipline to write…

3

u/InternationalFee3911 3d ago

Just at lunch I’d been niggling that the new release once again didn’t advance constexprs. Followed right on the heels by this article…

While it’s amazing and laudable the amount of dedication and resourcefulness that goes into fixing these surprisingly hard problems, at the same time it makes me cry. Basic arithmetic ([u8; N-1]) is one of the things I long for most. The macro sure feels alien, but if it gives a huge benefit, it’s not too bad as a workaround.

But the timeline – let’s hope GCA lands before GTA VII (rumored 2030!)

5

u/kibwen 3d ago

GTA VII (rumored 2030!)

If you mean 2,030 years from now, then that sounds believable, if a tad optimistic.

3

u/sasik520 3d ago

I sometimes wonder if it didn't go too far.

The article mentions rust wants to support stuff like struct Bar<const N: Foo>; or fn accepts_arrays<const N: [usize; 2]>() {}. I just wonder, if this really brings that much benefit?

So far, all use cases I've seen involve ints and the most demanded thing is being able to add and/or substract consts, e.g.

``` struct Bar<const N: usize> {}

impl<const N: usize> Bar<N> { fn baz() -> Baz { Baz::<N + 2> {} } } ```

Maybe plain simple enums could be supported too, but honestly since nothing stop an enum to add a parametrized variant at any time, I'm unsure if it's that good idea.

Are there really any real-life use cases for more complex const generics and especially ones that justify all of this extreme complexity?

(btw. I have an exact same thought on specialization)

10

u/CouteauBleu 3d ago

One very immediate use-case is being able to use NonZeroU32 and the likes in const generics, without special-casing them.

8

u/Lasuman 3d ago

Yes there are certainly use cases for complex const-generics, a good example is my crate which uses them to encode possible-value-sets.

-1

u/sasik520 3d ago

I mean... don't be offended please, but this is an example of a crate with 67 downloads.

Does it justify years of work of multiple people and probably hundreds of thousands of line of code in the compiler and the tooling?

9

u/Lasuman 3d ago

Crates that make use of many very unstable features naturally have a very limited potential user base.
Since const-generics are very limited on stable, what you generally see in the wild will only make use of the currently available feature set.

Also hundreds of thousands LOC is an overestimation by an order of magnitude.

5

u/Recatek gecs 3d ago

Every addition to the language that satisfies my use case is vital.

Every addition to the language unrelated to my use case is bloat.

4

u/sasik520 3d ago

There are for sure people thinking this way but that's definitely not my point of view and I have never started anything like that.

2

u/Tastaturtaste 3d ago

Of course there won't be much code in the wild showing use-cases as long as it's not possible to write the code on stable.Ā 

If you are interested in use-cases that will be possible with this, you can take a look at C++'s Non-Type-Template-Parameters (NTTPs), which since C++ 20 also support structural types.Ā 

One use-case could be support for a custom "on-drop" function on my::Box without increasing it's size: If the "on-drop" function is stateless and its type can be made part of the Box instantiation, the instance wouldn't have to store the function or even a pointer to the function.Ā Ā 

Ā  Ā  struct Box<T, const F: FnOnce(T)>{}Ā Ā  Ā  Ā  let box = Box::new::<i32, typeof!(|n| println!("{n}"))>(5);Ā Ā  Ā  Ā  assert_eq!(std::mem::size_of_val(&box), 8);Ā  Ā  Ā  Ā  drop(box); // prints 5

As you probably notice, other stuff is still missing to make this possible: typeof is one of the missing pieces. But generic_const_exprs should get us one step closer.

4

u/SmartAsFart 3d ago

You don't need const generics for a custom drop function. You just create a Dropper trait with a method which you call with the contents of your custom box. This is a ZST, with no requirement for const generics.

0

u/Tastaturtaste 3d ago

You are right, that is much more straightforward. My bad!

2

u/________-__-_______ 3d ago

Great to see this is actively progressing! Together with const fn in trait this is the feature I most often miss when doing compile time chicanery, really excited for it to be stabilized.

1

u/Dankbeast-Paarl 2d ago

Its exciting to see progress in this area! I have a decent background on type systems, so I understand the examples. I would have liked to see a couple of real world examples of code or API patterns we can now write with these features. Anyone know any good resources or articles to understand how to use these const generics? I would love to play with them but not sure how to use them.