SyntaxStudy
Sign Up
Rust Const Generics and Phantom Types
Rust Beginner 1 min read

Const Generics and Phantom Types

Const generics allow types and functions to be parameterised by constant values, not just types. The most obvious use is fixed-size arrays: `fn sum(arr: [i32; N]) -> i32` works for any compile-time-known array size. Const generics enable zero-overhead abstractions for matrix dimensions, buffer sizes, and other numeric parameters that would otherwise require either macros or runtime checks. Phantom types are a compile-time technique using the `PhantomData` marker to make a struct "aware" of a type parameter that does not appear in its fields. This is used to encode extra semantic information in the type system without any runtime cost. A common example is a typed ID: `struct Id(u64, PhantomData)` ensures that an `Id` and an `Id` cannot be accidentally confused even though they both wrap a `u64`. Together, const generics and phantom types push Rust's type system into the domain of dependent and refinement types. You can encode state machines, unit systems (metres vs. feet), protocol state (Connected vs. Disconnected), and access levels entirely in the type system, making illegal states unrepresentable and catching bugs that would otherwise only surface at runtime.
Example
use std::marker::PhantomData;

// --- Const generics ---
fn sum_array<const N: usize>(arr: [i32; N]) -> i32 {
    arr.iter().sum()
}

#[derive(Debug)]
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 {
        Matrix { data: [[0.0; COLS]; ROWS] }
    }
    fn shape(&self) -> (usize, usize) { (ROWS, COLS) }
}

// --- Phantom types ---
struct User;
struct Post;

#[derive(Debug, Clone, Copy)]
struct Id<T> {
    value: u64,
    _phantom: PhantomData<T>,
}

impl<T> Id<T> {
    fn new(value: u64) -> Self {
        Id { value, _phantom: PhantomData }
    }
}

fn get_user_name(_id: Id<User>) -> &'static str { "Alice" }
// fn get_user_name(_id: Id<Post>) — would be a type error at call site

fn main() {
    println!("sum [1,2,3] = {}", sum_array([1, 2, 3]));
    println!("sum [10,20] = {}", sum_array([10, 20]));

    let m: Matrix<3, 4> = Matrix::new();
    println!("matrix shape: {:?}", m.shape());

    let user_id: Id<User> = Id::new(1);
    let post_id: Id<Post> = Id::new(1);
    println!("user_id = {:?}", user_id);
    println!("post_id = {:?}", post_id);
    println!("user name = {}", get_user_name(user_id));
    // get_user_name(post_id); // compile error: expected Id<User>, got Id<Post>
}