到这个位置, 各种代码已经开始变得十分凌乱了. 开始着手搭建封装的框架. 这部分代码与上下文无关, 可以单独阅读, 不依赖其他内容.
该模块实现了一个擦除了类型的 Box 指针, 命名为 Ptr . 这里只实现了一个最小可用集 (MVP) 及其测试, 比如只给出了获取不可变引用的 fetch_as, 后续还会在此基础上扩充, 但内容大同小异, 因此只会在发生大的变动时写新文章做出记录.
类型擦除实现的关键工具是:
TypeId: 每个类型都有的一个唯一标识符.Any: rust 的动态类型.transmute: 将一个类型的对象强制转换成另一个类型, 是 unsafe 代码. 这里保存了类型信息并在强制转换时执行了类型检查, 同时不对外界提供 Ptr 中类型信息的修改权限, 保证了类型检查的可靠性.
#[derive(Debug)]
pub struct OwnPtr {
type_id: TypeId,
raw_ptr: Box<dyn Any>,
}
impl OwnPtr {
pub fn new<T: 'static>(from: T) -> Self {
Self { type_id: TypeId::of::<T>(), raw_ptr: Box::new(from) }
}
/// # Safety hint
/// The type information of Ptr matches the target conversion type.
pub fn read<T: 'static>(&self) -> Result<&Box<T>, ()> {
if TypeId::of::<T>() != self.type_id { return Err(()); }
let resource = &self.raw_ptr;
let destination = unsafe { transmute(resource) };
Ok(destination)
}
/// # Safety hint
/// The type information of Ptr matches the target conversion type.
pub fn write<T: 'static>(&mut self) -> Result<&mut Box<T>, ()> {
if TypeId::of::<T>() != self.type_id { return Err(()); }
let resource = &mut self.raw_ptr;
let destination = unsafe { transmute(resource) };
Ok(destination)
}
}
后续的目标是利用这个 Ptr 搭建一些可以在不使用 enum 的前提下储存不同类型的容器.
使用 enum 当然也可以储存不同类型, 但对于变动很快的游戏开发过程来说还是不够灵活, 每次增加新类型都要修改 enum 表项, 违反了开闭原则.