Const Generics in Rust: vs Type Generics Explained

Max WellsMax WellsFounder of Rustify

TL;DR: Const generics allow types and functions to be generic over constant values (integers, booleans, chars) rather than only types. The most important use case is arrays: [T; N] where N is a const generic; this is how std implements traits uniformly for arrays of any length. You write struct Matrix<const ROWS: usize, const COLS: usize> and the compiler generates separate code for each distinct const value used.


What Are Const Generics?

Const generics are compile-time constant values used as type parameters; most commonly array sizes.

struct Matrix<const ROWS: usize, const COLS: usize> {
    data: [[f64; COLS]; ROWS],
}
 
impl<const ROWS: usize, const COLS: usize> Matrix<ROWS, COLS> {
    fn new() -> Self {
        Self { data: [[0.0; COLS]; ROWS] }
    }
 
    fn rows(&self) -> usize { ROWS }
    fn cols(&self) -> usize { COLS }
}
 
let m: Matrix<3, 4> = Matrix::new(); // 3×4 matrix, zero-cost abstraction

Matrix<3, 4> and Matrix<5, 5> are distinct types at compile time; the compiler generates optimized code for each.


How Does std Use Const Generics?

Since Rust 1.51, [T; N] implements std traits uniformly for any N using const generics.

Before const generics, the standard library manually implemented traits like Debug, Clone, and PartialEq for arrays up to length 32. After const generics, one impl covers all sizes:

// This is how std now works internally:
impl<T: Debug, const N: usize> Debug for [T; N] {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { ... }
}

This is why you can now use [u8; 64].clone() without worrying about whether 64 is in the "supported" list.


How Do You Write a Function With Const Generics?

Declare the const parameter with const N: usize in the generic list.

fn first_n<T: Copy, const N: usize>(slice: &[T]) -> Option<[T; N]> {
    if slice.len() < N {
        return None;
    }
    let mut arr = [slice[0]; N];
    arr.copy_from_slice(&slice[..N]);
    Some(arr)
}
 
let arr: Option<[i32; 3]> = first_n(&[1, 2, 3, 4, 5]);

The caller can specify N explicitly or let the compiler infer it from context.


Const Generics vs Type Generics vs Trait Bounds in 2026

Const generics, type generics, and trait bounds each solve a different parameterization problem; knowing when to use which is key to idiomatic Rust.

Const GenericsType GenericsTrait Bounds
Parameterized overConstant values (usize, bool, char)TypesBehavior / capabilities
Compile-time dispatch✅ Monomorphized✅ Monomorphized✅ (static) / ❌ (dyn)
Use caseArray sizes, fixed buffersCollections, wrappersAbstracting over behavior
Stable sinceRust 1.51 (2021)AlwaysAlways
Arithmetic on params⚠️ Nightly (generic_const_exprs)N/AN/A
MaintainerRust lang teamRust lang teamRust lang team

In 2026, stable const generics cover the vast majority of real-world needs; fixed-size arrays, stack-allocated buffers, and compile-time dimension checks. Reach for type generics when abstracting over types, trait bounds when abstracting over behavior, and const generics when you need a type that carries a compile-time integer. Avoid typenum for new code; const generics replace it on stable Rust.


What Are the Current Limitations?

Const generic expressions (arithmetic on const params) are partially supported; complex expressions require nightly.

// Stable: simple const params
struct Buffer<const N: usize>([u8; N]);
 
// Nightly only: arithmetic in const expressions
// struct Doubled<const N: usize>([u8; N * 2]);  // requires #![feature(generic_const_exprs)]

On stable Rust, const parameters must be simple identifiers; you cannot use N + 1 or N * 2 as a type-level expression. The generic_const_exprs feature is tracked for stabilization.


Frequently Asked Questions

Stable Rust supports usize, u8u128, i8i128, bool, and char. Floats, strings, and structs are not yet supported as const generic parameters on stable.

Yes. The compiler monomorphizes each distinct const value, generating fully specialized code. There is no runtime dispatch or indirection.

typenum is a crate that encoded integers as types before const generics existed. It is largely superseded for new code in 2026, but older libraries (like nalgebra pre-2021) still use it for compatibility.

Use const generics when the value is known at compile time and you want the type system to enforce it; for example, [u8; 32] for a fixed-size crypto key. Use runtime parameters (usize fields) when the value varies per instance.

Yes. You can write trait FixedSize<const N: usize> { fn len() -> usize { N } }. This allows traits to be generic over a size parameter, enabling implementations like impl FixedSize<16> for MyKey {}.


Sources


  • generic: Type generics, the more common form of parameterization
  • associated-types: Another way to make traits flexible
  • const-static: Compile-time constants (different from const generics)
  • trait: Trait bounds are the companion tool to const generics
  • impl-trait: Return-position impl Trait, another compile-time dispatch tool

Keep Reading

Ready to Land a $80-120k Rust Job?