You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

3.0 KiB

PhantomData幽灵数据

在编写非安全代码时,我们常常遇见这种情况:类型或生命周期逻辑上与一个结构体关联起来了,但是却不属于结构体的任何一个成员。这种情况对于生命周期尤为常见。比如,&'a [T]Iter大概是这么定义的:

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

但是,因为'a没有在结构体内被使用它是无界的。由于一些历史原因无界生命周期和类型禁止出现在结构体定义中。所以我们必须想办法在结构体内用到这些类型这也是正确的变性检查和drop检查的必要条件。

我们使用一个特殊的标志类型PhantomData做到这一点。PhantomData不消耗存储空间它只是模拟了某种类型的数据以方便静态分析。这么做比显式地告诉类型系统你需要的变性更不容易出错而且还能提供drop检查需要的信息。

Iter逻辑上包含一系列&'a T,所以我们用PhantomData这样去模拟它:

use std::marker;

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

就是这样,生命周期变得有界了,你的迭代器对于'aT也可变了。一切尽如人意。

另一个重要的例子是Vec,它差不多是这么定义的:

struct Vec<T> {
    data: *const T, // *const是可变的
    len: usize,
    cap: usize,
}

和之前的例子不同,这个定义已经满足我们的各种要求了。Vec的每一个泛型参数都被至少一个成员使用过了。非常完美!

你高兴的太早了。

Drop检查器会判断Vec<T>并不拥有T类型的值然后它认为无需担心Vec在析构函数里能不能安全地销毁T再然后它会允许人们创建不安全的Vec析构函数。

为了让drop检查器知道我们确实拥有T类型的值也就是需要在销毁Vec的时候同时销毁T我们需要添加一个额外的PhantomData

use std::marker:

struct Vec<T> {
    data: *const T, // *const是可变的
    len: usize,
    cap: usize,
    _marker: marker::PhantomData<T>,
}

让裸指针拥有数据是一个很普遍的设计,以至于标准库为它自己创造了一个叫Unique<T>的组件,它可以:

  • 封装一个*const T处理变性
  • 包含一个PhantomData
  • 自动实现Send/Sync模拟和包含T时一样的行为
  • 将指针标记为NonZero以便空指针优化

PhantomData模式表

下表展示了各种牛X闪闪的PhantomData用法:

Phantom 类型 'a 'T
PhantomData<T> - 协变可触发drop检查
PhantomData<&'a T> 协变 协变
PhantomData<&'a mut T> 协变 不变
PhantomData<*const T> - 协变
PhantomData<*mut T> - 不变
PhantomData<fn(T)> - 逆变(*)
PhantomData<fn() -> T> - 协变
PhantomData<fn(T) -> T> - 不变
PhantomData<Cell<&'a ()>> 不变 -

(*)如果发生变性的冲突,这个是不变的