Bewildered by Vec<Box<dyn T>> and TypeId behaviour

Viewed 51
use std::any::Any;
use std::any::TypeId;

trait Task {}

struct SomeTask;
impl Task for SomeTask {}

fn main() {
    let tasks_a: Vec<Box<SomeTask>> = vec![Box::new(SomeTask)];
    let tasks_b: Vec<Box<dyn Any>> = vec![Box::new(SomeTask)];
    let tasks_c: Vec<Box<dyn Task>> = vec![Box::new(SomeTask)];

    println!(
        "Item stored in `Vec<Box<SomeTask>>` is of type `SomeTask`? {}",
        TypeId::of::<SomeTask>() == (&*tasks_a[0]).type_id(),
    );

    println!(
        "Item stored in `Vec<Box<dyn Any>>` is of type `SomeTask`? {}",
        TypeId::of::<SomeTask>() == (&*tasks_b[0]).type_id(),
    );

    println!(
        "Item stored in `Vec<Box<dyn Task>>` cast as `&dyn Any` is of type `SomeTask`? {}",
        TypeId::of::<SomeTask>() == (&tasks_c[0] as &dyn Any).type_id(),
    );

    println!(
        "Item stored in `Vec<Box<dyn Task>>` is of type `SomeTask`? {}",
        TypeId::of::<SomeTask>() == (&*tasks_c[0]).type_id(),
    );
}

(Playground)

Output:

Item stored in `Vec<Box<SomeTask>>` is of type `SomeTask`? true
Item stored in `Vec<Box<dyn Any>>` is of type `SomeTask`? true
Item stored in `Vec<Box<dyn Task>>` cast as `&dyn Any` is of type `SomeTask`? false
Item stored in `Vec<Box<dyn Task>>` is of type `SomeTask`? false

Can you help shed some light on the output? Specifically, I don't understand why item of T stored in Vec<Box<dyn Any>> is of type T, but that's not true for item stored in Vec<Box<dyn Trait>>.

1 Answers

When you call (&*tasks_b[0]).type_id(), you call <dyn Any as Any>::type_id(&self). This calls the type_id() method on the dyn Any through the dynamic vtable.

When you call (&*tasks_a[0]).type_id(), you call <SomeTask as Any>::type_id(&self). This calls this function statically, since the type is known.

The key is the (&*tasks_c[0]).type_id() call. &*tasks[0] is of type &dyn Task. This type does not define a dynamic type_id() method, because it is not dyn Any. So, the same as with the static SomeTask, we call a static type_id(). What is Self? Well, Self is dyn Task. This is a concrete type - dyn Trait is not just some type implementing the trait, it is a type itself that represents a type-erased type implementing the trait. And just like any other type, it has a TypeId. If you'll compare the result of (&*tasks_c[0]).type_id() to TypeId::of::<dyn Task>(), you'll see they're equal.

(&tasks_c[0] as &dyn Any).type_id() is similar, but not exactly the same: here we take &tasks_c[0], which has the type &Box<dyn Task>, and coerce it to &dyn Any - that is, the erased type is Box<dyn Task>, and indeed the TypeId is TypeId::of::<Box<dyn Task>>.

If you want type_id() on dyn Task to calculate the actual type ID of the erased type, you can make Any a supertrait of Task. This will not allow you to make a dyn Task into a dyn Any, however. This requires the unstable trait upcasting coercion. Until then, the usual way is to expose a method to upcast dyn Task into dyn Any in the trait:

trait Task: Any {
    fn as_any(&self) -> &dyn Any;
}

impl Task for SomeTask {
    fn as_any(&self) -> &dyn Any { self as &dyn Any }
}

tasks_c[0].as_any().type_id();
Related