本次实现的是一个类型擦除数组. 与 Rust 游戏引擎搭建 - 4. 类型擦除指针 - 掘金 (juejin.cn) 实现的目的类似, 都是为了在标准容器中储存不同数据类型.
比起称为“类型擦除”, 也许“类型隐藏”更为合适. 因为不管是指针还是数组, 内部都保存了类型信息以做校验, 实际上并不能随意转换.
这种数组我命名为 DataBlock.
DataBlock 需求说明
在已经实现了类型擦除指针后, 可以用这种指针的数组来模拟擦除了类型的数组. 不过, 数组的内存是连续分布的, 用指针来模拟就失去了内存连续带来的 cache 命中优势. 为了保留连续内存的同时擦除数据类型, 制作这一抽象结构.
功能描述
- 支持基础的数组数据操作, 包括 new, push, get, get_mut.
- 边界检查, 类型检查.
Example 测试
#[test]
fn push_and_get() {
let mut block = DataBlock::new::<Integer>();
block.push(&Integer(12)).unwrap();
let read = block.get::<Integer>(0).unwrap();
assert_eq!(read.0, 12);
block.push(&Integer(32)).unwrap();
let write = block.get_mut::<Integer>(1).unwrap();
assert_eq!(write.0, 32);
write.0 = 22;
let read = block.get::<Integer>(1).unwrap();
assert_eq!(read.0, 22);
}
实现细节
这一节主要的难点和此前的 Ptr 实现一样, 在于类型的擦除与复原.
Struct 定义
首先是数据存储的形式:
pub struct DataBlock {
type_id: TypeId,
data: Vec<u8>,
}
擦除了类型的数据以 u8 的形式存储在 data 字段中, 同时保留类型信息作为校验.
&[u8] 与 Struct 的转换
unsafe fn serialize_raw<T: Sized>(input: &T) -> &[u8] {
let type_raw = input as *const T;
let data_raw = type_raw as *const u8;
std::slice::from_raw_parts(data_raw, size_of::<T>())
}
unsafe fn deserialize_raw<T: Sized>(input: &[u8]) -> Option<&T> {
let data_raw = input.as_ptr();
let type_raw = data_raw as *const T;
type_raw.as_ref()
}
unsafe fn deserialize_raw_mut<T: Sized>(input: &mut [u8]) -> Option<&mut T> {
let data_raw = input.as_mut_ptr();
let type_raw = data_raw as *mut T;
type_raw.as_mut()
}
这几个 unsafe 的辅助函数实现了类型信息的擦除和复原. 在实现过程中没有任何校验操作, 因此无法保证操作的安全性, 需要在函数声明出标注 unsafe.
数组基础操作
有了类型转换, 操作就顺理成章了:
impl DataBlock {
pub fn new<T: Sized + 'static>() -> Self {
Self {
type_id: TypeId::of::<T>(),
data: Vec::new(),
}
}
pub fn push<T: Sized + 'static>(&mut self, value: &T) -> Result<(), &'static str> {
if self.type_id != TypeId::of::<T>() {
return Err("The type of the value doesn't match.");
}
let slice = unsafe { serialize_raw(value) };
for byte in slice.iter() {
self.data.push(byte.clone());
}
Ok(())
}
pub fn get<T: Sized + 'static>(&self, index: usize) -> Option<&T> {
if self.type_id != TypeId::of::<T>() { return None; }
let (start, end) = self.boundary::<T>(index)?;
let slice = &self.data[start..end];
unsafe { deserialize_raw(slice) }
}
pub fn get_mut<T: Sized + 'static>(&mut self, index: usize) -> Option<&mut T> {
if self.type_id != TypeId::of::<T>() { return None; }
let (start, end) = self.boundary::<T>(index)?;
let slice = &mut self.data[start..end];
unsafe { deserialize_raw_mut(slice) }
}
fn boundary<T: Sized>(&self, index: usize) -> Option<(usize, usize)> {
let size = size_of::<T>();
let start = size * index;
let end = start + size;
if end > self.data.len() { None }
else { Some((start, end)) }
}
}
函数不长, 名字也写的比较清楚, 那我就摸了.