The best way to understand it is to implement one! This SO question:
is a good starting point. The implementation in it relies on std::any::Any
but doesn't shed light on why 'static
is required. If it is to be implemented directly in the same vein as (dyn Any)::downcast_ref()
though (playground):
use std::any::TypeId;
trait A {
fn type_id(&self) -> TypeId
where
Self: 'static,
{
TypeId::of::<Self>()
}
}
impl dyn A + 'static {
fn is<T: A + 'static>(&self) -> bool {
let t = TypeId::of::<T>();
let concrete = self.type_id();
t == concrete
}
fn downcast_ref<T: A + 'static>(&self) -> Option<&T> {
if self.is::<T>() {
unsafe { Some(&*(self as *const dyn A as *const T)) }
} else {
None
}
}
}
struct B;
impl A for B {}
fn main() {
let a: Box<dyn A> = Box::new(B);
let _b: &B = match a.downcast_ref::<B>() {
Some(b) => b,
None => panic!("&a isn't a B!"),
};
}
The unsafe
here is actually safe because we are only downcasting back to the original type thanks to the type id check. The implementation pivots on TypeId
to give us that guarantee:
A TypeId is currently only available for types which ascribe to 'static, but this limitation may be removed in the future.
If digging a little bit deeper, the way how this type id is calculated at the moment finally gives us the answer. That is, a struct Struct<'a>
may have N instantiations with different concrete substitutions for 'a
, but the type id is the same. Were the TypeId
to be available to all Struct<'a>
, we then can downcast any Struct<'a>
to Struct<'static>
, which would be a soundness issue.