Rust Const Generics Cheat Sheet
Parameterizing types and functions over compile-time constant values, array-length generics, and const expressions in bounds.
Basic Const Generics
Parameterize a type over a compile-time constant, most commonly an array length.
struct Matrix<const R: usize, const C: usize> { data: [[f64; C]; R],}impl<const R: usize, const C: usize> Matrix<R, C> { fn zero() -> Self { Matrix { data: [[0.0; C]; R] } } fn rows(&self) -> usize { R }}let m: Matrix<3, 4> = Matrix::zero(); // R=3, C=4 fixed at compile time
Generic Functions Over Array Length
Write one function that works for any array size N.
fn sum<const N: usize>(arr: [i32; N]) -> i32 { arr.iter().sum()}let total = sum([1, 2, 3]); // N inferred as 3let total2 = sum([1, 2, 3, 4, 5]); // N inferred as 5// Const generics also work with referencesfn first_n<const N: usize>(slice: &[i32]) -> Option<[i32; N]> { slice.get(..N)?.try_into().ok()}
Const Params in Trait Bounds & Defaults
Const generics can have defaults and interact with where clauses.
struct Buffer<const SIZE: usize = 1024> { data: [u8; SIZE],}let default_buf: Buffer = Buffer { data: [0; 1024] }; // uses default SIZElet small_buf: Buffer<64> = Buffer { data: [0; 64] };// Const expressions in bounds (stabilized incrementally, check MSRV)fn double_buf<const N: usize>() -> [u8; N * 2]where [(); N * 2]:,{ [0; N * 2]}
What's Stable vs. Still Limited
Const generics are powerful but arithmetic-in-types support is incremental.
- const N: usize params- fully stable for structs, enums, functions, impls
- array [T; N]- the most common use case, stable
- min_const_generics- the stabilized baseline feature (Rust 1.51+)
- const expressions (N + 1)- generic_const_exprs still nightly-only / unstable as of 2026
- const generics with traits- e.g. impl<const N: usize> Trait for [T; N], works for fixed small N via std
Working Around Unstable Const Exprs
Compute derived constants via a helper trait's associated const instead of an in-type expression, which keeps you on stable Rust.
trait DoubleOf<const N: usize> { const VALUE: usize;}struct Doubler<const N: usize>;impl<const N: usize> DoubleOf<N> for Doubler<N> { const VALUE: usize = N * 2; // computed once, associated const, stable}struct Buffer<const N: usize>where Doubler<N>: DoubleOf<N>,{ data: [u8; N],}// Callers read the derived value via <Doubler<N> as DoubleOf<N>>::VALUE// instead of writing `[u8; N * 2]` directly in a signature.
Generic Trait Impls Over Array Length
Const generics let one impl block cover every array size, replacing the old macro-generated impls up to a fixed N.
trait Zeroed { fn zeroed() -> Self;}impl<T: Default + Copy, const N: usize> Zeroed for [T; N] { fn zeroed() -> Self { [T::default(); N] }}#[derive(Debug)]struct Wrapper<T, const N: usize>([T; N]);impl<T, const N: usize> From<[T; N]> for Wrapper<T, N> { fn from(arr: [T; N]) -> Self { Wrapper(arr) }}let w: Wrapper<i32, 5> = [1, 2, 3, 4, 5].into();
Compile-Time Assertions on Const Params
Force a const-eval error at monomorphization time to reject invalid const generic combinations before runtime.
struct FixedBlock<const N: usize>;impl<const N: usize> FixedBlock<N> { const CHECK: () = assert!(N > 0 && N % 8 == 0, "N must be a positive multiple of 8"); fn new() -> Self { let _ = Self::CHECK; // forces evaluation, fails to compile for bad N FixedBlock }}let ok = FixedBlock::<16>::new(); // compiles// let bad = FixedBlock::<7>::new(); // compile error: assertion failed
Const Generics + PhantomData for Typestate
Encode a compile-time state or capacity tag alongside a zero-sized marker, giving zero runtime cost.
use std::marker::PhantomData;struct Locked;struct Unlocked;struct Ring<const CAP: usize, State = Unlocked> { buf: [u8; CAP], len: usize, _state: PhantomData<State>,}impl<const CAP: usize> Ring<CAP, Unlocked> { fn lock(self) -> Ring<CAP, Locked> { Ring { buf: self.buf, len: self.len, _state: PhantomData } }}impl<const CAP: usize> Ring<CAP, Locked> { fn capacity(&self) -> usize { CAP } // only callable once locked}
Const Generics Vocabulary
Terms you'll hit reading RFCs, tracking issues, and nightly feature gates.
- min_const_generics- the stable Rust 1.51 baseline: bare const N: usize params, no exprs
- generic_const_exprs- nightly feature allowing arithmetic like [T; N * 2] directly in signatures
- adt_const_params- nightly feature to use custom structs/enums (not just integers) as const params
- const_evaluatable_checked- older name for the const-expr-in-bounds checking machinery, now folded into generic_const_exprs
- monomorphization- each distinct const generic value (Matrix<3,4> vs Matrix<4,3>) generates its own compiled code
- where [(); EXPR]:- the classic stable-Rust trick to force evaluation of a const expr as a bound
When you hit 'generic parameters may not be used in const operations', it usually means you need the still-unstable generic_const_exprs feature — restructure to pass the derived constant explicitly as its own const generic parameter instead of computing it in-type.