Rust 游戏引擎搭建 - 4. 类型擦除指针

384 阅读1分钟

到这个位置, 各种代码已经开始变得十分凌乱了. 开始着手搭建封装的框架. 这部分代码与上下文无关, 可以单独阅读, 不依赖其他内容.

该模块实现了一个擦除了类型的 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 表项, 违反了开闭原则.