1. 1. Introduction
  2. 2. Meet Safe and Unsafe
    1. 2.1. How Safe and Unsafe Interact
    2. 2.2. Working with Unsafe
  3. 3. Data Layout
    1. 3.1. repr(Rust)
    2. 3.2. Exotically Sized Types
    3. 3.3. Other reprs
  4. 4. Ownership
    1. 4.1. References
    2. 4.2. Lifetimes
    3. 4.3. Limits of Lifetimes
    4. 4.4. Lifetime Elision
    5. 4.5. Unbounded Lifetimes
    6. 4.6. Higher-Rank Trait Bounds
    7. 4.7. Subtyping and Variance
    8. 4.8. Drop Check
    9. 4.9. PhantomData
    10. 4.10. Splitting Borrows
  5. 5. Type Conversions
    1. 5.1. Coercions
    2. 5.2. The Dot Operator
    3. 5.3. Casts
    4. 5.4. Transmutes
  6. 6. Uninitialized Memory
    1. 6.1. Checked
    2. 6.2. Drop Flags
    3. 6.3. Unchecked
  7. 7. Ownership Based Resource Management
    1. 7.1. Constructors
    2. 7.2. Destructors
    3. 7.3. Leaking
  8. 8. Unwinding
    1. 8.1. Exception Safety
    2. 8.2. Poisoning
  9. 9. Concurrency
    1. 9.1. Races
    2. 9.2. Send and Sync
    3. 9.3. Atomics
  10. 10. Implementing Vec
    1. 10.1. Layout
    2. 10.2. Allocating
    3. 10.3. Push and Pop
    4. 10.4. Deallocating
    5. 10.5. Deref
    6. 10.6. Insert and Remove
    7. 10.7. IntoIter
    8. 10.8. RawVec
    9. 10.9. Drain
    10. 10.10. Handling Zero-Sized Types
    11. 10.11. Final Code
  11. 11. Implementing Arc and Mutex

PhantomData

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

struct Iter<'a, T: 'a> {
    ptr: *const T,
    end: *const T,
}Run

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

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

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

use std::marker;

struct Iter<'a, T: 'a> {
    ptr: *const T,
    end: *const T,
    _marker: marker::PhantomData<&'a T>,
}Run

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

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

struct Vec<T> {
    data: *const T, // *const for variance!
    len: usize,
    cap: usize,
}Run

Unlike the previous example, it appears that everything is exactly as we want. 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 values of type T. This will in turn make it conclude that it doesn't need to worry about Vec dropping any T's in its destructor for determining drop check soundness. This will in turn allow people to create unsoundness using Vec's destructor.

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

use std::marker;

struct Vec<T> {
    data: *const T, // *const for covariance!
    len: usize,
    cap: usize,
    _marker: marker::PhantomData<T>,
}Run

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