Advanced Generics and Type-Level Programming

Generic Constants

Generic constants are compile-time const items associated with a trait (const ID: u32;) or a generic impl<T> Struct<T> block (pub const SIZE: usize = ...;). Unlike module-level const items, which have a single fixed type and value, generic associated constants can compute a different compile-time value for every concrete type T or const parameter N.

Associated Constants in Traits

A trait can declare an associated constant—with or without a default value—and implementations provide a compile-time constant expression for each concrete type:

trait FixedWireFormat: Sized {
    const WIRE_SIZE: usize = size_of::<Self>();
    const MAGIC_BYTE: u8;
}

#[repr(C)]
struct PingPacket {
    timestamp: u64,
}

impl FixedWireFormat for PingPacket {
    const MAGIC_BYTE: u8 = 0xA1;
}

#[repr(C)]
struct AckPacket {
    seq: u32,
    status: u8,
}

impl FixedWireFormat for AckPacket {
    // Override default `WIRE_SIZE` to exclude trailing struct padding on the wire:
    const WIRE_SIZE: usize = 5;
    const MAGIC_BYTE: u8 = 0xB2;
}

fn describe_packet<P: FixedWireFormat>(name: &str) {
    println!(
        "{name}: magic = {:#04x}, wire_size = {} bytes (mem size = {} bytes)",
        P::MAGIC_BYTE,
        P::WIRE_SIZE,
        size_of::<P>()
    );
}

fn main() {
    describe_packet::<PingPacket>("PingPacket");
    describe_packet::<AckPacket>("AckPacket");
}
PingPacket: magic = 0xa1, wire_size = 8 bytes (mem size = 8 bytes)
AckPacket: magic = 0xb2, wire_size = 5 bytes (mem size = 8 bytes)

Generic Inherent Constants and Compile-Time Layout Assertions

An inherent impl<T, const N: usize> MyType<T, N> block can define associated const items that depend on T and N. Because associated constants are only evaluated when referenced (monomorphization-time evaluation), placing assert!(...) inside an associated const acts as a post-monomorphization compile-time check:

use std::marker::PhantomData;

struct BoundedChunk<T, const COUNT: usize> {
    _marker: PhantomData<[T; COUNT]>,
}

impl<T, const COUNT: usize> BoundedChunk<T, COUNT> {
    // Evaluated at compile time for each concrete `(T, COUNT)` that calls `new()`:
    const VALIDATE_SIZE: () = {
        assert!(COUNT > 0, "COUNT must be at least 1");
        assert!(
            size_of::<T>() * COUNT <= 64,
            "BoundedChunk total byte size must fit within a 64-byte cache line"
        );
    };

    pub const TOTAL_BYTES: usize = size_of::<T>() * COUNT;

    pub fn new() -> Self {
        let () = Self::VALIDATE_SIZE;
        Self {
            _marker: PhantomData,
        }
    }
}

fn main() {
    let _valid = BoundedChunk::<u64, 8>::new();
    println!(
        "BoundedChunk<u64, 8>::TOTAL_BYTES = {} bytes",
        BoundedChunk::<u64, 8>::TOTAL_BYTES
    );
}
BoundedChunk<u64, 8>::TOTAL_BYTES = 64 bytes

If a caller writes BoundedChunk::<u64, 9>::new(), compilation fails during const evaluation with the exact panic message "BoundedChunk total byte size must fit within a 64-byte cache line".

Comparing Generic Constants and Const Generic Parameters

Feature Const Generic Parameter (struct S<const N: usize>) Associated Constant (Trait::N or Struct::<T>::N)
Specified by Caller in type turbofish S::<16> Trait or struct implementation for T
Allowed types Primitive integers (u8..usize, i8..isize), char, bool Any const-constructible type (including &'static str, structs, tuples, arrays)
Usable directly in [u8; Self::N] array type? Yes ([u8; N] works in stable Rust) Only when Self is concrete; generic [u8; T::N] requires nightly generic_const_exprs