Use PhantomData<T> for Variance and Ownership with Pointer Generics

2026-08-30 浏览 (1)


id: ptr-03 original_id: P.UNS.PTR.03 level: P impact: HIGH

Use PhantomData for Variance and Ownership with Pointer Generics

Summary

When a struct contains raw pointers but logically owns or borrows the pointed-to data, use PhantomData<T> to tell the compiler about the relationship.

Rationale

Raw pointers don't carry ownership or lifetime information. PhantomData lets you:

  • Indicate ownership (for Drop check)
  • Control variance (covariant, contravariant, invariant)
  • Participate in lifetime elision

Bad Example

// DON'T: Raw pointer without PhantomData
struct MyVec<T> {
    ptr: *mut T,
    len: usize,
    cap: usize,
}

// Problems:
// 1. Compiler doesn't know we "own" the T values
// 2. T might be incorrectly determined as unused
// 3. Drop check may allow dangling references

Good Example

use std::marker::PhantomData;
use std::ptr::NonNull;

// DO: Use PhantomData to express ownership
struct MyVec<T> {
    ptr: NonNull<T>,
    len: usize,
    cap: usize,
    _marker: PhantomData<T>,  // We own T values
}

// For owned data: PhantomData<T>
// For borrowed data: PhantomData<&'a T>
// For mutably borrowed: PhantomData<&'a mut T>
// For function pointers: PhantomData<fn(T)> (contravariant)

// DO: Express lifetime relationships
struct Iter<'a, T> {
    ptr: *const T,
    end: *const T,
    _marker: PhantomData<&'a T>,  // Borrows T for 'a
}

impl<'a, T> Iterator for Iter<'a, T> {
    type Item = &'a T;

    fn next(&mut self) -> Option<Self::Item> {
        if self.ptr == self.end {
            None
        } else {
            // SAFETY: ptr < end, so ptr is valid
            // Lifetime is tied to 'a through PhantomData
            let current = unsafe { &*self.ptr };
            self.ptr = unsafe { self.ptr.add(1) };
            Some(current)
        }
    }
}

PhantomData Patterns

Phantom TypeMeaningVariance
PhantomData<T>Owns TCovariant
PhantomData<&'a T>Borrows T for 'aCovariant in T, covariant in 'a
PhantomData<&'a mut T>Mutably borrows TInvariant in T, covariant in 'a
PhantomData<*const T>Just has pointerCovariant
PhantomData<*mut T>Just has pointerInvariant
PhantomData<fn(T)>Consumes TContravariant
PhantomData<fn() -> T>Produces TCovariant

Drop Check

use std::marker::PhantomData;

// This tells the compiler that dropping MyVec may drop T values
struct MyVec<T> {
    ptr: NonNull<T>,
    _marker: PhantomData<T>,
}

impl<T> Drop for MyVec<T> {
    fn drop(&mut self) {
        // Drop all T values...
    }
}

// Without PhantomData<T>, this might compile incorrectly:
// let x = MyVec::new(&local);
// drop(local);  // Would be UB if allowed
// drop(x);      // Tries to access dropped local

Checklist

  • Does my pointer type logically own the pointed-to data?
  • Do I need to express a lifetime relationship?
  • What variance do I need for my generic parameter?
  • Will the type be dropped, and does it need drop check?

Related Rules

  • ptr-02: Prefer NonNull over *mut T
  • safety-05: Send/Sync implementation safety

你可能感兴趣的文章

Do Not Expose Raw Pointers in Public APIs

Unsafe Code Authors Must Verify Safety Invariants

Ensure Consistent Data Layout for Custom Types

Add Safety Section in Docs for Public Unsafe Functions

Do Not Use Union Variants Across Different Lifetimes

Use MaybeUninit for Uninitialized Memory

Ensure C-ABI Compatibility for Strings Between Rust and C

Do Not Dereference Pointers Cast to Misaligned Types

Use Dedicated Opaque Type Pointers Instead of c_void for C Opaque Types

Choose Appropriate Data Layout for Struct/Tuple/Enum

  • 所属分类: AI
  • 本文标签: rust
  • 版权声明: 本文链接 https://seaxiang.com/blog/ToziaIXq