Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.syntblaze.com/llms.txt

Use this file to discover all available pages before exploring further.

Const generics are generic parameters that range over compile-time constant values rather than types or lifetimes. They allow structs, enums, traits, and functions to be parameterized by specific values, enabling the compiler to generate monomorphized implementations bound to those exact constants.

Syntax and Declaration

A const generic parameter is declared using the const keyword, followed by an identifier, a colon, and a concrete type.
// A struct parameterized by a type `T` and a constant `N` of type `usize`
struct StaticBuffer<T, const N: usize> {
    data: [T; N],
}

// A function parameterized by a constant `LENGTH`
fn compute_hash<const LENGTH: usize>(payload: [u8; LENGTH]) {
    // Function body
}

Type Restrictions

As of current stable Rust, the types permitted for const generic parameters are strictly limited to a subset of primitives. You cannot use standard library types (like String), custom structs, or floating-point numbers. Permitted types include:
  • Unsigned integers: u8, u16, u32, u64, u128, usize
  • Signed integers: i8, i16, i32, i64, i128, isize
  • char
  • bool

Instantiation and Inference

When instantiating a type or calling a function with a const generic, the value provided must be a constant expression. The compiler can often infer the const value from the context, particularly when working with arrays.
// Explicit instantiation using the turbofish syntax
let buffer = StaticBuffer::<u32, 256> { 
    data: [0; 256] 
};

// Implicit inference based on the passed array length
let payload = [1, 2, 3, 4, 5];
compute_hash(payload); // `LENGTH` is automatically inferred as 5

Const Expressions and Disambiguation

When passing a constant expression (rather than a simple literal or a single identifier) as a generic argument, the expression must be enclosed in braces {}. This disambiguates the expression from standard generic syntax, preventing parsing ambiguities with operators like < and >.
const BASE_SIZE: usize = 64;

// The expression must be wrapped in {}
let buffer = StaticBuffer::<u8, { BASE_SIZE * 2 }> {
    data: [0; 128],
};
Note on Stable Limitations: While you can use constant expressions to instantiate a const generic, performing arithmetic operations on a const generic parameter within a type signature (e.g., fn split<const N: usize>() -> [u8; { N / 2 }]) requires the #![feature(generic_const_exprs)] nightly feature.

Type System Implications

Const generics create strictly distinct types at compile time. Two types with identical type parameters but different const parameters are entirely incompatible in the type system.
let buf_small: StaticBuffer<u8, 16> = StaticBuffer { data: [0; 16] };
let buf_large: StaticBuffer<u8, 32> = StaticBuffer { data: [0; 32] };

// ERROR: Expected StaticBuffer<u8, 16>, found StaticBuffer<u8, 32>
// buf_small = buf_large; 
Because they are distinct types, they do not share a memory layout or vtable. The compiler monomorphizes a separate copy of the implementation for every unique value of N encountered in the program.

Parameter Ordering

When combining lifetimes, types, and const generics, Rust enforces a specific declaration order to maintain parsing consistency. The standard ordering is:
  1. Lifetimes ('a)
  2. Types (T)
  3. Const generics (const N: usize)
// Correct ordering: Lifetime, Type, Const
struct ComplexBuffer<'a, T, const N: usize> {
    reference: &'a [T; N],
}
(Note: Recent Rust versions have relaxed ordering restrictions between types and consts, but placing lifetimes first remains mandatory, and the above ordering remains the idiomatic standard).
Master Rust with Deep Grasping Methodology!Learn More