Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

English Original

4. PhantomData —— 不携带数据的类型 🔶

你将学到:

  • 为什么 PhantomData<T> 存在以及它解决的三个问题
  • 生命周期烙印 (Lifetime Branding) 用于编译时作用域强制执行
  • 单位测量模式 (Unit-of-measure Pattern) 用于维度安全算术
  • 型变(协变、逆变、不变性)以及 PhantomData 如何控制它

PhantomData 解决了什么

PhantomData<T> 是一种零大小的类型,它告诉编译器:“尽管该结构体不包含 T,但在逻辑上它与 T 相关联。”它会影响型变 (Variance)、析构检查 (Drop Checking) 和自动 trait 推导 (Auto-trait Inference) —— 且不占用任何内存。

#![allow(unused)]
fn main() {
use std::marker::PhantomData;

struct Slice<'a, T> {
    ptr: *const T,
    len: usize,
    _marker: PhantomData<&'a T>,
    // 现在编译器知道了:
    // 1. 该结构体借用了生命周期为 'a 的数据
    // 2. 它对 'a 是协变的(生命周期可以缩小)
    // 3. 析构检查会考虑 T
}
}

1. 生命周期烙印 (Lifetime Branding)

使用 PhantomData 来防止混用来自不同“会话 (Sessions)”或“上下文 (Contexts)”的值:

#![allow(unused)]
fn main() {
struct ArenaHandle<'arena> {
    index: usize,
    _brand: PhantomData<&'arena ()>,
}
}

这确保了即使两个 Arena(内存池)内部表示相同,你也无法在 Arena B 中使用来自 Arena A 的句柄。

2. 单位测量模式 (Unit-of-measure Pattern)

通过零运行时成本,在编译时防止混用不兼容的单位:

#![allow(unused)]
fn main() {
struct Meters;
struct Seconds;

struct Quantity<Unit> {
    value: f64,
    _unit: PhantomData<Unit>,
}

let dist = Quantity::<Meters>::new(100.0);
let time = Quantity::<Seconds>::new(10.0);
// let sum = dist + time; // ❌ 编译错误:米 != 秒
}

型变 (Variance):为什么它很重要

型变 (Variance) 决定了泛型类型是否可以被其子类型或超类型替换。

型变 (Variance)“是否可以替换……”Rust 示例
协变 (Covariant)在期望 'short 的地方使用 'long&'a T
逆变 (Contravariant)在期望 'long 的地方使用 'shortfn(T) (作为参数)
不变 (Invariant)不允许任何替换 ❌&mut T, Cell<T>

PhantomData 型变速查表

PhantomData 类型针对 T 的型变针对 'a 的型变
PhantomData<T>协变
PhantomData<&'a T>协变协变
PhantomData<&'a mut T>不变协变
PhantomData<*mut T>不变
PhantomData<fn(T)>逆变

实践建议:默认从 PhantomData<&'a T> (协变) 开始。只有当你需要通过此抽象分发对内部数据的可变访问权限时,才切换到 &'a mut T (不变性)。