查看原文
其他

Rust中的幽灵数据 PhantomData

秋风不度镇南关 码农真经 2023-12-25

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 type T, 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所不具备的


继续滑动看下一个

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存