Const Generics: This Rust Feature Cut Our Code by 85%
An Afternoon That Raised My Blood Pressure
An old colleague asked me to grab lunch last week. Over dinner, he mentioned a cryptography library he recently took over.
“You won’t believe it,” he put down his chopsticks, exhaustion in his voice. “The code is an absolute disaster.”
I asked what happened.
He said their team wrote a face recognition library three years ago. To support matrix operations of different sizes, they used macros to generate 16 almost identical codebases. Sixteen! Each one just changed a number — 16, 32, 64, 128… like Russian nesting dolls.
“Do you know how many lines we ended up with?” He held up a finger. “8,347 lines. And that was after trimming.”
I thought to myself, this isn’t coding, it’s code graveyard.
“What happened later?” I asked.
“Later?” He gave a bitter laugh. “Every time I fixed a bug, I had to make the same change in 16 places. Forget one place, and the production server crashed the next day. I lost a third of my hair in six months — look at me now.”
I looked at his noticeably thinning hair and sighed internally.
Until Rust 1.51 Arrived
“The turning point came in March 2021,” he took a sip of soup, his eyes finally lighting up. “Rust 1.51 stabilized const generics.”
I was confused. “Const what?”
He wiped his mouth and started explaining. “Ever heard of tailor-made clothing? Back in the day, after taking your measurements — height, shoulder width, waist — the tailor would cut a piece of fabric just for you. But Rust’s generic system had a big bug before — it could only tell you ’this is a piece of clothing,’ but couldn’t tell you ’this is a size XL.'”
“Is that it?” I thought he was making a big deal out of nothing. “Hear me out,” he waved his hand. “It’s not just about changing sizes. You know what? Before this, if you wanted to write a function that handles fixed-size arrays, you couldn’t write generic code.”
He grabbed a napkin and sketched a diagram:
// Before: Want a generic function? No way
fn process_16(data: [u8; 16]) { /* process 16 bytes */ }
fn process_32(data: [u8; 32]) { /* process 32 bytes */ }
fn process_64(data: [u8; 64]) { /* process 64 bytes */ }
// ... write 16 functions like this
“See?” he said. “Each function only has a different number, the logic is identical. But the compiler doesn’t buy it. It says 16 and 32 are different types — you have to write them separately.”
I got it. It’s like going to a courier service and they say, “Sorry, we only recognize box models, not box sizes.” You say you want to ship a small box, and they say, “For small boxes, we have Type A, Type B, Type C… Which one do you want?”
“So how does const generics solve this?” I was getting interested.
He smiled: “It lets the compiler know that 16, 32, and 64 are actually family.”
// Now: One function handles all sizes
fn process<const N: usize>(data: [u8; N]) {
// N is the array size, known at compile time
}
That’s it? One const N: usize turns 16 functions into 1.
“That’s literally it,” he nodded vigorously. “When I saw this syntax, I was stunned. Three years! We struggled for three years, just waiting for this syntax sugar.”
How 85% of the Code Disappeared
After lunch, we moved to a coffee shop. He insisted on showing me their refactored data. “Do you know how many lines we have now?” He handed me his phone. “1,243 lines.”
I calculated — from 8,347 to 1,243, that’s about 85% less. “But that’s not the scariest part,” he said mysteriously. “Guess what the performance is like?”
I shook my head.
“Small array operations are 83% faster. Compile time dropped from 23 seconds to 9 seconds. Binary size went from 847KB to 507KB.”
I almost spat out my coffee: “Wait, 85% less code, but almost double the performance? You’re kidding?”
He shook his head seriously. “No kidding. There are two reasons. First, const generics lets the compiler do optimizations it couldn’t do before. Like SIMD auto-vectorization — once the compiler knows the array size, it can fully unroll loops and prefetch data.”
“And the second?”
“Second,” he lowered his voice, “the code generated by macros had a lot of redundant memory operations. With const generics, the compiler can do better inlining and constant folding. A lot of computation happens at compile time, not runtime.”
He opened his laptop and showed me some code:
// Before: 16 implementations, each doing dynamic allocation
fn multiply_16(a: &[f32; 16], b: &[f32; 16]) -> Vec<f32> {
let mut result = Vec::with_capacity(16); // heap allocation!
// ... calculation
result
}
// Now: Stack-based, zero allocation
fn multiply<const N: usize>(a: &[f32; N], b: &[f32; N]) -> [f32; N] {
let mut result = [0.0; N]; // stack allocation, size known at compile time
// ... same calculation
result
}
“See?” he said. “Before, every matrix multiplication had to allocate new memory on the heap. Now it computes directly on the stack. Memory allocation is expensive — if you can save it, you should.”
Const Generics in Everyday Life
I asked him to explain const generics with an everyday scenario. He thought for a moment and said, “Ever bought flat-pack furniture?”
“Yeah, IKEA.”
“You know IKEA cabinets, right? Each model has a fixed number of parts. The instruction manual says: cabinet A needs 4 side panels, 2 top panels, 3 shelves… But if you wanted to make a universal assembly robot, what would you do?”
I shook my head. “Before, you’d write a separate program for each cabinet model. Cabinet A uses program A, cabinet B uses program B, cabinet C uses program C… 16 models, 16 programs.”
“After const generics, you just write one program: cabinet
I got it: “So const generics parameterizes those ‘models’?”
“Exactly,” he was happy I understood. “Models are constants. The number and dimensions of models can be passed as type parameters. The compiler can immediately see how big the cabinet is and how many panels it needs, no runtime lookup required.”
Those Head-Scratching Limitations
“Though,” I poured some cold water, “there must be limits, right?”
He nodded. “Of course. You can’t put arbitrary operations in const parameters. For example, if you want to write impl<const N * N: usize>, sorry, not supported. Multiplication and more complex operations don’t work on the type level yet.”
Also, you can’t do heap allocation in const context. You can’t write const fn create_buffer() -> Vec<u8>, because Vec is a runtime thing.
“What do you do when you hit these issues?““Work around it,” he said. “Pre-compute constants. If you need N*N, compute it outside first: const SIZE: usize = 4; const SQUARED: usize = SIZE * SIZE; then use Matrix<SIZE, SQUARED>. The compiler isn’t dumb — it knows what SQUARED is.”
He also reminded me that const generics increases compile time because each different constant value generates a new code instance. But this trade-off is usually worth it, since runtime is faster.
What Did We Get Out of All This?
The coffee got cold, but we were still chatting hot. “To summarize,” I helped him organize his thoughts, “what does const generics give us?”
He counted on his fingers:
“First, massive code reduction. No need to write the same code twice — the DRY principle finally stands up in array processing.”
“Second, massive performance gains. Stack instead of heap, compile-time optimization instead of runtime computation, SIMD vectorization instead of serial loops. Everything that can be saved, is saved.”
“Third, type safety. The compiler checks array sizes for us, catches bugs at compile time, no waiting for production to crash.”
“Fourth, maintainability. Before, fixing bugs in 16 places. Now, fix one place. New hires can read the code, no more getting lost in macro expansions.”
He paused, then added: “What got me most emotional was our new grad. He used to get scalp migraines looking at macro-generated code. Now seeing const generics syntax, he said, ‘This is just regular generics, I can read this.’ I almost cried hearing that.”
So When Should You Use It?
“So when should I use this feature?” I asked finally.
He thought about it: “When you meet these conditions: array size is fixed and known at compile time; you care about performance and want zero-copy zero-allocation; your code starts having lots of repeated patterns, and not writing macros hurts, but writing macros hurts more.”
“On the flip side, if your array size is user input, or you’re still in rapid prototyping phase, don’t use it yet. const generics makes compilation slower, and it doesn’t help with sizes determined at runtime.”
“At the end of the day,” he summarized, “it’s a tool, not a silver bullet. Used in the right place, it’s a godsend. Used in the wrong place, it’s a burden.”
I looked out the window — it was dark. “Before we go,” I asked, “how’s that cryptography library doing now?”
He stood up and stretched. “It’s doing great. 67% fewer bugs, new hires can get up to speed in three months. Our team can finally have lunch together instead of working overtime fixing bugs every day.”
He patted my shoulder: “Listen, if you have a lot of fn process_16, fn process_32 functions in your code, try switching to const generics. You’ll come back and thank me.”
Cheat Sheet
Remember This One Line: const generics lets type parameters carry “size” — like tailor-made clothing, precise to the millimeter.
Use It When:
- The array size is fixed and known at compile time
- You’re a performance freak and want zero-copy zero-allocation
- You catch yourself using macros to copy-paste
Don’t Use It When:
- Size is determined at runtime by user input
- Your project already compiles painfully slow
- You’re in rapid prototyping mode
Lessons Learned the Hard Way:
- Can’t do complex arithmetic in const parameters — the type system isn’t that smart yet
- const functions can’t do heap allocation — they’re compile-time only
- Each different constant value generates new code (trading compile time for execution time)
Found This Article Helpful?
- Like: If it helped, give it a like so more people can see
- Share: Share with friends or colleagues who might need it
- Follow: Follow Monster Programming for more practical tech articles
- Comment: Have you ever had to write N similar functions in your project? How did you solve it?
Your support is what keeps me going!
