Rust中的幽灵数据 PhantomData
PhantomData, 虚类型数据,也叫幽灵数据,按照字面意思,该数据类型虚无缥缈,就像不存在一样。在Rust中PhantomData不占用内存空间,那它能有什么用武之地么?
我们先来看下该数据类型的定义:
pub struct PhantomData<T>where
T: ?Sized;
官网API中解释到
Zero-sized type used to mark things that "act like" they own a
T
.Adding a
PhantomData<T>
field to your type tells the compiler that your type acts as though it stores a value of typeT
, even though it doesn't really. This information is used when computing certain safety properties.
翻译过来就是
零大小类型,用于标记对象表现得像他们拥有一个 T
添加一个PhantomData<T>字段到自定义类型中,告诉编译器,该类型保存有一个类型T的数据成员(尽管他并不真的是拥有该类型数据)。该信息将有助于计算特定的安全问题。
也就是说PhantomData是用来帮助编译器做检查的,基于这点,就有2个用途
1. 未使用的声明周期
2. 未使用的类型参数(type parameter)
下面是这2个场景的使用例子
1.未使用的声明周期
我们采用API文档中的例子来说明这一情况
以下例子中,结构体Slice这样定义的意图是,Slice中的start, end的生命周期要和 ‘a 保持一致, 当 'a 结束时, start/end要失效。
但从原始定义的代码中,我们并未看到有 'a被使用到
struct Slice<'a, T> { // 原始定义start: *const T,
end: *const T,
}
我们使用PhantomData来修改下
struct Slice<'a, T: 'a> {start: *const T,
end: *const T,
phantom: PhantomData<&'a T>,
}
fn main() {
let data = vec![0; 3];
let slice = borrow_vec(&data); // slice中的生命周期'a 和 data的生命周期一致
drop(data);//销毁 data, slice的生命周期 'a也跟着消失了, slice 也就跟着销毁了
let b:*const i32 = slice.start; // data已销毁, slice内部start指针已经被销毁,故报错
println!("{:?}", b);
}
fn borrow_vec<T>(vec: &Vec<T>) -> Slice<'_, T> {
let ptr = vec.as_ptr();
Slice {
start: ptr,
end: unsafe { ptr.add(vec.len()) },
phantom: PhantomData,
}
}
运行上面的代码,可以看到报错,这和我们的预期一致
error[E0505]: cannot move out of `data` because it is borrowed
--> src\main.rs:68:10
|
67 | let slice = borrow_vec(&data);
| ----- borrow of `data` occurs here
68 | drop(data);
| ^^^^ move out of `data` occurs here
69 | let b:*const i32 = slice.start;
| ----------- borrow later used here
2. 未使用的类型参数(type parameter)
这种方式,API文档也有个例子,但说得不够清晰(本人也不太理解),我们使用另外的例子来说明。
这个特性可以用来实现非常巧妙的状态模式,而且是类型安全的状态模式。
以下例子描述了一个订单系统,该系统提供如下服务,各个服务都要满足一定条件才能提供服务
1. 生成空订单
2. 添加商品到订单: 约束条件 -> 订单中必须没有商品
3. 设置订单地址: 约束条件 -> 订单中的地址还没填写
4. 寄送订单: 约束条件 -> 订单必须是未完成,且订单中要有商品,且订单地址也已填好
代码如下
fn main() {let order: Order<InCompleteOrder, NoItem, NoAddress> = Order::<InCompleteOrder, NoItem, NoAddress>::emptyOrder();
let order: Order<InCompleteOrder, ItemProvided, NoAddress> = OrderingSystem::set_item("basketball", order);
let order: Order<InCompleteOrder, ItemProvided, AddressProvided> = OrderingSystem::set_shipping("Shenzhen", order);
let order: Order<OrderCompleted, ItemProvided, AddressProvided> = OrderingSystem::place_order(order);
println!("order {:?}", order);
}
#[derive(Debug)] struct OrderCompleted;
#[derive(Debug)] struct InCompleteOrder;
#[derive(Debug)] struct ItemProvided;
#[derive(Debug)] struct NoItem;
#[derive(Debug)] struct AddressProvided;
#[derive(Debug)] struct NoAddress;
struct Order<A, B, C> {
item_id: Option<String>,
shipping_address: Option<String>,
_a: PhantomData<A>,
_b: PhantomData<B>,
_c: PhantomData<C>,
}
impl<A, B, C> Order<A, B, C> {
///创建空订单
fn emptyOrder() -> Order<InCompleteOrder, NoItem, NoAddress> {
Order {
item_id: None,
shipping_address: None,
_a: PhantomData,
_b: PhantomData,
_c: PhantomData,
}
}
}
impl<A, B, C> Debug for Order<A, B, C> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "item:{:?}, shipping address:{:?}", self.item_id, self.shipping_address)
}
}
mod OrderingSystem {
use super::*;
///设置订单商品
///
///订单商品必须为空,才可设置
pub(crate) fn set_item<A, B>(item: &str, o: Order<A, NoItem, B>) -> Order<A, ItemProvided, B> {
println!("adding new item {}", item);
Order {
item_id: Some(item.to_string()),
shipping_address: o.shipping_address,
_a: PhantomData,
_b: PhantomData,
_c: PhantomData,
}
}
///设置订单地址
///
/// 订单地址必须未填,方可设置
pub(crate) fn set_shipping<A, B>(address: &str, o: Order<A, B, NoAddress>) -> Order<A, B, AddressProvided> {
println!("adding shipping address {}", address);
Order {
item_id: o.item_id,
shipping_address: Some(address.to_owned()),
_a: PhantomData,
_b: PhantomData,
_c: PhantomData,
}
}
///发送订单
///
/// 订单必须未完成,且订单商品已设置,订单地址也已设置
pub(crate) fn place_order(o: Order<InCompleteOrder, ItemProvided, AddressProvided>) -> Order<OrderCompleted, ItemProvided, AddressProvided> {
println!("placing order..");
Order {
item_id: o.item_id,
shipping_address: o.shipping_address,
_a: PhantomData,
_b: PhantomData,
_c: PhantomData,
}
}
}
如果我们刚创建了空订单,就发送订单,则会编译出错,例子如下
fn main() {let empty_order: Order<InCompleteOrder, NoItem, NoAddress> = Order::<InCompleteOrder, NoItem, NoAddress>::emptyOrder();
let order: Order<OrderCompleted, ItemProvided, AddressProvided> = OrderingSystem::place_order(empty_order);
println!("order {:?}", order);
}
error[E0308]: mismatched types
--> src\main.rs:63:99
|
63 | let order: Order<OrderCompleted, ItemProvided, AddressProvided> = OrderingSystem::place_order(empty_order);
| ^^^^^^^^^^^ expected struct `ItemProvided`, found struct `NoItem`
|
= note: expected struct `Order<_, ItemProvided, AddressProvided>`
found struct `Order<_, NoItem, NoAddress>`
Summary:
1. Rust的PhantomData, 不仅有助于编译器进行安全检查,也提供了新的编程范式 -> 实现类型安全的状态模式。
2. 由此更加说明了Rust是多范式的编程语言,具备底层的能力和高层的抽象,这是c, c++, java, go, python, ruby所不具备的