PhantomData

When working with unsafe code, we can often end up in a situation wheretypes or lifetimes are logically associated with a struct, but not actuallypart of a field. This most commonly occurs with lifetimes. For instance, theIter for &'a [T] is (approximately) defined as follows:

  1. struct Iter<'a, T: 'a> {
  2. ptr: *const T,
  3. end: *const T,
  4. }

However because 'a is unused within the struct’s body, it’s unbounded.Because of the troubles this has historically caused, unbounded lifetimes andtypes are forbidden in struct definitions. Therefore we must somehow referto these types in the body. Correctly doing this is necessary to havecorrect variance and drop checking.

We do this using PhantomData, which is a special marker type. PhantomDataconsumes no space, but simulates a field of the given type for the purpose ofstatic analysis. This was deemed to be less error-prone than explicitly tellingthe type-system the kind of variance that you want, while also providing otheruseful things such as the information needed by drop check.

Iter logically contains a bunch of &'a Ts, so this is exactly what we tellthe PhantomData to simulate:

  1. use std::marker;
  2. struct Iter<'a, T: 'a> {
  3. ptr: *const T,
  4. end: *const T,
  5. _marker: marker::PhantomData<&'a T>,
  6. }

and that’s it. The lifetime will be bounded, and your iterator will be variantover 'a and T. Everything Just Works.

Another important example is Vec, which is (approximately) defined as follows:

  1. struct Vec<T> {
  2. data: *const T, // *const for variance!
  3. len: usize,
  4. cap: usize,
  5. }

Unlike the previous example, it appears that everything is exactly as wewant. Every generic argument to Vec shows up in at least one field.Good to go!

Nope.

The drop checker will generously determine that Vec<T> does not own any valuesof type T. This will in turn make it conclude that it doesn’t need to worryabout Vec dropping any T’s in its destructor for determining drop checksoundness. This will in turn allow people to create unsoundness usingVec’s destructor.

In order to tell dropck that we do own values of type T, and therefore maydrop some T’s when we drop, we must add an extra PhantomData saying exactlythat:

  1. use std::marker;
  2. struct Vec<T> {
  3. data: *const T, // *const for variance!
  4. len: usize,
  5. cap: usize,
  6. _marker: marker::PhantomData<T>,
  7. }

Raw pointers that own an allocation is such a pervasive pattern that thestandard library made a utility for itself called Unique<T> which:

  • wraps a *const T for variance
  • includes a PhantomData<T>
  • auto-derives Send/Sync as if T was contained
  • marks the pointer as NonZero for the null-pointer optimization

Table of PhantomData patterns

Here’s a table of all the wonderful ways PhantomData could be used:

Phantom type 'a T
PhantomData<T> - variant (with drop check)
PhantomData<&'a T> variant variant
PhantomData<&'a mut T> variant invariant
PhantomData<*const T> - variant
PhantomData<*mut T> - invariant
PhantomData<fn(T)> - contravariant (*)
PhantomData<fn() -> T> - variant
PhantomData<fn(T) -> T> - invariant
PhantomData<Cell<&'a ()>> invariant -

(*) If contravariance gets scrapped, this would be invariant.