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 |