Course contentsShow
← Rust
Lesson 1138 of 1,91010. Concurrency PrimitivesFree lesson

The PhantomData Trick in Concurrent Structs

You will use zero-sized types to inform the compiler about ownership requirements for non-Send fields.

In this lesson, you will learn how to use PhantomData to influence the Send and Sync traits of your concurrent structs when they contain non-thread-safe fields.

The Problem of Hidden Send-ability

When building concurrent systems, you often create structs that wrap raw pointers or FFI-bound resources. These types frequently implement !Send or !Sync because the compiler cannot guarantee they are safe to access across thread boundaries. However, sometimes you know that your struct is safe to move to another thread, but the compiler is overly cautious because of a specific field type.

The PhantomData Trick

PhantomData<T> is a zero-sized type that acts as a placeholder. It consumes no memory at runtime but tells the compiler, "Trust me, this struct behaves as if it owns a T."

We use this in concurrency to "inject" the safety characteristics of a phantom type into our own struct. If your struct contains a raw pointer that makes it !Send, but you’ve implemented external synchronization, you can use PhantomData to restore the desired marker traits.

Consider this example:

use std::marker::PhantomData;

struct ConcurrentManager<T> {
    raw_ptr: *mut T,
    // Even though raw pointers are !Send, we inform 
    // the compiler this struct acts as if it contains T.
    _marker: PhantomData<T>,
}

// Now we can safely implement Send for our struct, 
// telling the compiler it's okay to move across threads.
unsafe impl<T: Send> Send for ConcurrentManager<T> {}

By adding _marker: PhantomData<T>, you are essentially "attaching" the thread-safety requirements of T to your ConcurrentManager. If T is Send, your manager becomes Send. If T is not Send, the compiler will correctly prevent you from accidentally moving your struct between threads.

This trick is essential when your struct's thread safety is derived from its internal data rather than the physical memory layout of the struct itself. It keeps your concurrency patterns explicit and safe without requiring unsafe blocks everywhere in your logic.

Key Takeaway: Use PhantomData<T> to propagate Send and Sync requirements from your generic type parameters to your custom structs, ensuring the compiler enforces concurrency rules correctly.