Rust 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>, }
就是这样,生命周期变得有界了,你的迭代器对于'a
和T
也可变了。一切尽如人意。
另一个重要的例子是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 ()>> |
不变 | - |
(*)如果发生变性的冲突,这个是不变的
可变引用的Mutex属性在处理复合类型时能力非常有限。借用检查器只能理解一些简单的东西,而且极易失败。他对结构体还算是充分了解,知道结构体的成员可能被分别借用。所以这段代码现在可以正常工作:struct Foo { a ...