Every once in a while there comes a use case where the functionality of Any would be desired in a non-static context. Unfortunately, one of the sticky points in the Rust library is that TypeId::of<T>
requires that the lifetime of T to be 'static. Here are two of the incredibly fun solutions I have found and discovered.
Dtolnay, the genius they are, exploits the monomorphization of functions to use the function pointers as IDs:
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
pub struct TypeId {
id: usize,
}
impl TypeId {
pub fn of<T: ?Sized>() -> Self {
TypeId {
id: TypeId::of::<T> as usize,
}
}
}
My solution is different, and uses type_name. It relies on two things:
-
type_name
conveniently left out the 'static bound -
type_name
returns a&'static str
, which means that that string is valid for the lifetime of the program.
Here it is (all the stuff that makes this not contrived has been omitted):
pub trait AnyLike<'a, T>
where
T: 'a,
{
fn type_id(&self) -> usize {
std::any::type_name::<Self>().as_ptr() as usize
}
}
Assuming that type_name
prints out the fully qualified path of each type (which it is NOT guaranteed to do), this works across crates! I wouldn't recommend it though.
It's hard to believe I need to actually use this to do something legitimate, but I do, and these two solutions are all I have.
Which is fine for me, as I've carefully thought about the assertions and any possible safety issues. This code is primarily going to be used for unit testing, and I know that all of the types I'm using this on will have distinct names even if the paths are squished. Also, none is named with the prefix of another, which I suppose could also produce a false positive.
However, I'm worried that other people might have not thought about their problems as deeply as I have, and we may end up being de facto required to have type_name print the full path all of the time before we know it.