Loading page…
Rust walkthroughs
Loading page…
PhantomData is a zero-sized type that tells the compiler your type "logically owns" a T, even though it doesn't physically contain one. It's a way to express type relationships, lifetime constraints, and ownership semantics in your types.
Key use cases:
TPhantomData has zero runtime cost — it compiles to nothing. It's purely a compile-time construct for expressing type system constraints.
use std::marker::PhantomData;
struct MyStruct<T> {
_marker: PhantomData<T>,
value: i32,
}
fn main() {
let s: MyStruct<String> = MyStruct {
_marker: PhantomData,
value: 42,
};
println!("Value: {}", s.value);
println!("Size of MyStruct: {} bytes", std::mem::size_of::<MyStruct<String>>());
// Size is just the i32 (4 bytes), PhantomData is zero-sized
}use std::marker::PhantomData;
// A generic type where T doesn't appear in any field
struct Container<T> {
data: Vec<u8>,
_marker: PhantomData<T>,
}
impl<T> Container<T> {
fn new(data: Vec<u8>) -> Self {
Self {
data,
_marker: PhantomData,
}
}
fn into_inner(self) -> Vec<u8> {
self.data
}
}
// Type-safe API even though T isn't stored
struct Json;
struct Xml;
fn main() {
let json: Container<Json> = Container::new(b"{\"key\": \"value\"}".to_vec());
let xml: Container<Xml> = Container::new(b"<key>value</key>".to_vec());
// Can't mix them up!
fn process_json(c: Container<Json>) {
println!("Processing JSON: {} bytes", c.data.len());
}
fn process_xml(c: Container<Xml>) {
println!("Processing XML: {} bytes", c.data.len());
}
process_json(json);
process_xml(xml);
}use std::marker::PhantomData;
// A type that logically holds a reference but doesn't store it
struct Iter<'a, T> {
ptr: *const T,
len: usize,
_marker: PhantomData<&'a T>, // Express the lifetime relationship
}
impl<'a, T> Iter<'a, T> {
fn new(slice: &'a [T]) -> Self {
Self {
ptr: slice.as_ptr(),
len: slice.len(),
_marker: PhantomData,
}
}
fn get(&self, index: usize) -> Option<&'a T> {
if index < self.len {
// Safe because we know ptr is valid for 'a
Some(unsafe { &*self.ptr.add(index) })
} else {
None
}
}
}
fn main() {
let data = vec![1, 2, 3, 4, 5];
let iter = Iter::new(&data);
println!("First: {:?}", iter.get(0));
println!("Last: {:?}", iter.get(4));
}use std::marker::PhantomData;
// Different ownership semantics with PhantomData
// This type acts like it owns a T (affects drop check)
struct Owns<T> {
ptr: *mut T,
_marker: PhantomData<T>, // Owning semantics
}
// This type acts like it holds a reference to T
struct Borrows<'a, T> {
ptr: *const T,
_marker: PhantomData<&'a T>, // Borrowing semantics
}
// This type acts like it holds a mutable reference to T
struct MutBorrows<'a, T> {
ptr: *mut T,
_marker: PhantomData<&'a mut T>, // Mutable borrowing semantics
}
fn main() {
let mut value = 42;
let owns: Owns<i32> = Owns {
ptr: &mut value,
_marker: PhantomData,
};
let borrows: Borrows<i32> = Borrows {
ptr: &value,
_marker: PhantomData,
};
println!("Pointer addresses: {:?}, {:?}", owns.ptr, borrows.ptr);
}use std::marker::PhantomData;
// Not Send or Sync because of raw pointer
struct RawContainer {
ptr: *mut i32,
}
// By adding PhantomData, we can control auto-traits
struct SendableContainer {
ptr: *mut i32,
_marker: PhantomData<i32>, // Makes it Send if i32: Send
}
// Note: This is unsafe! Raw pointers are not Send/Sync for safety reasons.
// Only do this if you know the pointer is thread-safe.
unsafe impl Send for SendableContainer {}
// To explicitly NOT be Send/Sync:
struct NotSend {
_marker: PhantomData<*const i32>, // *const i32 is !Send
}
fn main() {
fn check_send<T: Send>() {}
fn check_sync<T: Sync>() {}
check_send::<SendableContainer>();
// check_send::<NotSend>(); // Would fail to compile
println!("Container types created");
}use std::marker::PhantomData;
// Covariant in T (can substitute T with a subtype)
struct Covariant<T> {
_marker: PhantomData<fn() -> T>, // Covariant
}
// Contravariant in T (can substitute T with a supertype)
struct Contravariant<T> {
_marker: PhantomData<fn(T)>, // Contravariant
}
// Invariant in T (cannot substitute at all)
struct Invariant<T> {
_marker: PhantomData<fn(T) -> T>, // Invariant
}
fn main() {
// &'a T is covariant in both 'a and T
// This means &'static str can be used where &'short str is expected
fn take_covariant<T>(_: Covariant<T>) {}
fn take_contravariant<T>(_: Contravariant<T>) {}
fn take_invariant<T>(_: Invariant<T>) {}
let cov: Covariant<&'static str> = Covariant { _marker: PhantomData };
take_covariant(cov); // Works
println!("Variance examples compiled");
}use std::marker::PhantomData;
struct NoDrop {
data: i32,
}
impl Drop for NoDrop {
fn drop(&mut self) {
println!("NoDrop::drop called");
}
}
// Without PhantomData, this wouldn't drop NoDrop
struct Wrapper {
ptr: *mut NoDrop,
_marker: PhantomData<NoDrop>, // Tells compiler we "own" NoDrop
}
impl Drop for Wrapper {
fn drop(&mut self) {
println!("Wrapper::drop called");
unsafe {
drop(Box::from_raw(self.ptr));
}
}
}
fn main() {
let ptr = Box::into_raw(Box::new(NoDrop { data: 42 }));
let wrapper = Wrapper {
ptr,
_marker: PhantomData,
};
// When wrapper is dropped, it will drop the NoDrop
drop(wrapper);
}use std::marker::PhantomData;
// States
struct Empty;
struct Filled;
struct Buffer<State> {
data: Vec<u8>,
_state: PhantomData<State>,
}
impl Buffer<Empty> {
fn new() -> Self {
Self {
data: Vec::new(),
_state: PhantomData,
}
}
fn fill(self, data: Vec<u8>) -> Buffer<Filled> {
Buffer {
data,
_state: PhantomData,
}
}
}
impl Buffer<Filled> {
fn data(&self) -> &[u8] {
&self.data
}
fn clear(self) -> Buffer<Empty> {
Buffer {
data: Vec::new(),
_state: PhantomData,
}
}
}
fn main() {
let empty_buffer: Buffer<Empty> = Buffer::new();
// Can't call data() on empty buffer!
// empty_buffer.data(); // Compile error!
let filled_buffer = empty_buffer.fill(vec![1, 2, 3, 4]);
// Now we can access data
println!("Data: {:?}", filled_buffer.data());
let empty_again = filled_buffer.clear();
// Can't call data() anymore!
// empty_again.data(); // Compile error!
}use std::marker::PhantomData;
// Different measurement units
struct Meters;
struct Feet;
struct Kilometers;
#[derive(Debug)]
struct Distance<Unit> {
value: f64,
_unit: PhantomData<Unit>,
}
impl<Unit> Distance<Unit> {
fn new(value: f64) -> Self {
Self {
value,
_unit: PhantomData,
}
}
fn value(&self) -> f64 {
self.value
}
}
impl Distance<Meters> {
fn to_kilometers(self) -> Distance<Kilometers> {
Distance::new(self.value / 1000.0)
}
fn to_feet(self) -> Distance<Feet> {
Distance::new(self.value * 3.28084)
}
}
fn main() {
let meters = Distance::<Meters>::new(1000.0);
println!("{:?} meters", meters.value());
let kilometers = meters.to_kilometers();
println!("{:?} kilometers", kilometers.value());
let meters2 = Distance::<Meters>::new(100.0);
let feet = meters2.to_feet();
println!("{:?} feet", feet.value());
}use std::marker::PhantomData;
use std::ops::Deref;
use std::ptr::NonNull;
struct MyBox<T> {
ptr: NonNull<T>,
_marker: PhantomData<T>,
}
impl<T> MyBox<T> {
fn new(value: T) -> Self {
let ptr = NonNull::new(Box::into_raw(Box::new(value))).unwrap();
Self {
ptr,
_marker: PhantomData,
}
}
}
impl<T> Deref for MyBox<T> {
type Target = T;
fn deref(&self) -> &T {
unsafe { self.ptr.as_ref() }
}
}
impl<T> Drop for MyBox<T> {
fn drop(&mut self) {
unsafe {
drop(Box::from_raw(self.ptr.as_ptr()));
}
}
}
fn main() {
let boxed = MyBox::new(42);
println!("Value: {}", *boxed);
}use std::marker::PhantomData;
use std::ffi::c_void;
// Opaque handle from C library
type c_handle = *mut c_void;
// Type-safe wrapper around C handle
struct Handle<T> {
raw: c_handle,
_marker: PhantomData<T>,
}
// Different resource types
struct FileHandle;
struct NetworkHandle;
impl<T> Handle<T> {
unsafe fn from_raw(raw: c_handle) -> Self {
Self {
raw,
_marker: PhantomData,
}
}
fn as_raw(&self) -> c_handle {
self.raw
}
}
// Type-specific operations
impl Handle<FileHandle> {
fn read(&self, buf: &mut [u8]) -> usize {
// Call C function: c_file_read(self.raw, buf.ptr, buf.len)
0
}
}
impl Handle<NetworkHandle> {
fn send(&self, data: &[u8]) {
// Call C function: c_network_send(self.raw, data.ptr, data.len)
}
}
fn main() {
// Type-safe handles prevent mixing different resource types
let file: Handle<FileHandle> = unsafe { Handle::from_raw(std::ptr::null_mut()) };
let net: Handle<NetworkHandle> = unsafe { Handle::from_raw(std::ptr::null_mut()) };
// Can't call send() on file handle!
// file.send(&[]); // Compile error!
println!("Handles created with type safety");
}use std::marker::PhantomData;
fn main() {
// 1. Unused type parameter
struct Generic<T> {
_marker: PhantomData<T>,
}
// 2. Covariant lifetime
struct CovariantLifetime<'a> {
_marker: PhantomData<&'a ()>,
}
// 3. Invariant lifetime (for mutability)
struct InvariantLifetime<'a> {
_marker: PhantomData<&'a mut ()>,
}
// 4. Ownership semantics
struct Owns<T> {
_marker: PhantomData<T>,
}
// 5. Not Send/Sync
struct NotThreadSafe {
_marker: PhantomData<*const ()>,
}
// 6. Type state
struct Stateful<S> {
_marker: PhantomData<S>,
}
println!("PhantomData patterns demonstrated");
}PhantomData Variants and Their Effects:
| Form | Variance | Send/Sync | Drop Check |
|------|----------|-----------|------------|
| PhantomData<T> | Covariant | Inherits from T | Yes |
| PhantomData<&'a T> | Covariant | Inherits from T | No |
| PhantomData<&'a mut T> | Invariant | Inherits from T | No |
| PhantomData<fn() -> T> | Covariant | Always | No |
| PhantomData<fn(T)> | Contravariant | Always | No |
| PhantomData<fn(T) -> T> | Invariant | Always | No |
| PhantomData<*const T> | Covariant | !Send, !Sync | No |
When to Use PhantomData:
| Use Case | Pattern |
|----------|---------|
| Unused type parameter | PhantomData<T> |
| Express lifetime | PhantomData<&'a T> |
| Make invariant | PhantomData<Cell<T>> or PhantomData<fn(T) -> T> |
| Not Send/Sync | PhantomData<*const T> or PhantomData<Rc<T>> |
| Ownership semantics | PhantomData<T> |
Key Points:
PhantomData<T> is zero-sized (no runtime cost)T