std::any::TypeId unique's identifier has no guarantees to stay constant between compilations or Rust versions. I wanted a TypeId that could be serialized and deserialized. Basically, I needed a way to map a type to an identifier, and vice versa, based on a table I defined myself. Here's a simple solution that doesn't use dependencies. I haven't seen this mentionend anywhere else so I thought I'd write about it.
As mentionned, this method uses a look up table that you define manually, based on the types you want to have type IDs for.
First, define your TypeId trait. We can seal it if needed, so it doesn't escape the defined file. (This may be useful if you want your look up table to be defined in the same file, and nowhere else.)
// Optional: sealing the trait. See https://predr.ag/blog/definitive-guide-to-sealed-traits-in-rust/
mod sealed {
pub trait Sealed {}
}
pub trait StableTypeId: sealed::Sealed {
const ID: u32
}
I use a short macro to define StableTypeId entries, but you can also define them manually. The Sealed impl is not necesasry if you don't want to seal the trait.
macro_rules! type_id {
($t:ty, $id:expr) => {
impl sealed::Sealed for $t {}
impl StableTypeId for $t {
const ID: u32 = $id;
}
}
}
This macro can then be used like so:
use crate::{StructA, StructB};
type_id!(StructA, 1);
type_id!(StructB, 2);
To look up a type id given a type, you can use this function:
const fn type_id_of<T:ConsistentTypeId>() -> u32 {
T::ID
}
And if you store the type somewhere, then load it with deserialization, it's easy to check against a generic's type ID. This lets you push the type checking at runtime, but still use generics for type safety:
fn get<T: StableTypeId, Deserlialize>(self, index: usize) -> Option<T> {
if self.get_entry(i).type_id != T::ID {
return None
}
self.load::<T>(i)
}