pub trait MyFoo {
fn as_any(&self) -> &dyn std::any::Any;
}
impl MyFoo for f32 {
fn as_any(&self) -> &dyn Any {
self}}
impl MyFoo for u32 {
fn as_any(&self) -> &dyn Any {
self}}
#[test]
fn it_works() {
let a = 1_u32;
let b = 1_f32;
let a_ptr: &dyn MyFoo = &a;
let b_ptr: &dyn MyFoo = &b;
println!("{:?}", a_ptr.as_any().downcast_ref::<u32>().is_some());
println!("{:?}", a_ptr.as_any().downcast_ref::<f32>().is_some());
println!("{:?}", b_ptr.as_any().downcast_ref::<u32>().is_some());
println!("{:?}", b_ptr.as_any().downcast_ref::<f32>().is_some());}
this outputs t, f, f, t, as expected.
Question: how does this work. There seems to be no 'space' in the f32/u32 to store a tag. Is the type stored in the &dyn MyFoo itself ?
EDIT: To the best of my current mental model, for the downcast to behave correctly, as it does above, we need to store some 'tag' somewhere. I don't see how there is space to store this tag in the u32 / f32 itself. Therefore, where is it stored? Is it stored in the &dyn MyFoo ?
Yes, this is one of the reason why a dyn Trait is unsized and so &dyn Trait is a fat pointer. One of the sub-pointers points to the "data" (the value of the original underlying type) itself, while the other one points to the vtable, which holds type information.
The TypeId isn't in the vtable as far as I know. But the Any trait has the type_id method, so the base type's TypeId is still available via method call.
Indeed, there is no type information in the vtable itself, but it still at least uniquely identifies the type (my wording was intentionally vague because I wasn't sure exactly what usage of the vtable allows one to get to a concrete type). So based on that Any impl, it looks like that the type information is encoded in the body of the Any::type_id() method itself.
pub fn downcast_ref<T: Any>(&self) -> Option<&T> {
if self.is::<T>() {
// SAFETY: just checked whether we are pointing to the correct type, and we can rely on
// that check for memory safety because we have implemented Any for all types; no other
// impls can exist as they would conflict with our impl.
unsafe { Some(&*(self as *const dyn Any as *const T)) }
} else {
None
}
}
pub fn is<T: Any>(&self) -> bool {
// Get `TypeId` of the type this function is instantiated with.
let t = TypeId::of::<T>();
// Get `TypeId` of the type in the trait object (`self`).
let concrete = self.type_id();
// Compare both `TypeId`s on equality.
t == concrete
}