In this lesson, you will learn how to use PhantomData to enforce type safety at compile time without adding any memory overhead to your structures.
The Problem: Ghost Parameters
Sometimes, you want a struct to be generic over a type, but you don't actually use that type in any of your fields. For example, imagine a Quantity<Unit> struct that tracks whether a measurement is in Meters or Feet. If you don't store a field of type Unit, the Rust compiler will complain: "parameter T is never used."
You could add a field to the struct, but that would waste memory at runtime. Instead, we use a "Phantom Data Type."
Using PhantomData
The std::marker::PhantomData type acts as a placeholder. It tells the compiler, "Trust me, this struct behaves as if it contains a value of type T," even though the struct remains empty in the final binary.
use std::marker::PhantomData;
struct Meters;
struct Feet;
struct Quantity<Unit> {
value: f64,
_marker: PhantomData<Unit>,
}
fn main() {
let length_in_meters = Quantity::<Meters> {
value: 10.0,
_marker: PhantomData
};
// The compiler now knows length_in_meters is specifically for Meters!
}
Why This Matters
Because PhantomData is a zero-sized type (ZST), it disappears during the process of monomorphization. It takes up exactly zero bytes in your compiled machine code.
By using it, you turn type parameters into "marker types." You aren't storing data; you are essentially tagging your struct with a label that the compiler can use to prevent you from accidentally adding Meters to Feet in your business logic. You effectively create a state machine that exists only during compilation, ensuring that your program is logically correct before it ever runs.
Key Takeaway: Use PhantomData<T> to associate a type parameter with a struct to enforce state safety without incurring any runtime memory or performance cost.