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

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>>.

What you've done is cast &Box<dyn Trait> to &dyn Any. But the TypeId for Box<dyn Trait> is not the same as the type of T. On the other hand, when you did (&Box<dyn Trait>).type_id() it dereferenced the Box and called the type_id() method on the Any trait implementation, which is just T.type_id().

1 Like

Thanks for the quick reply! I get it that the 3rd println is wrongly using Box's type_id rather than inner value's type_id. But I still don't understand the part on this:

Item stored in Vec<Box<dyn Any>> is of type SomeTask, whereas
Item stored in Vec<Box<dyn Task>> is not type SomeTask

What I mean is that when you try to call type_id() on Box<dyn Any>, it will dereference the box to get a &dyn Any (which is actually a &SomeTask) and then call its type_id() method.

When you call (&tasks_c[0] as &dyn Any).type_id(), you are actually asking for the TypeId for a Box<dyn Task>, which is its own type with a separate TypeId.

There is an Any implementation for every non-'static type, so (&*tasks_c[0]).type_id() ends up calling the type_id() method on dyn Trait which is also its own distinct type and TypeId::of::<dyn Trait>().

Put differently, your last two print statements are the equivalent of this:

assert_eq!(
    TypeId::of::<SomeTask>(),
    TypeId::of::<Box<dyn Task>>(),
);

assert_eq!(
    TypeId::of::<SomeTask>(),
    TypeId::of::<dyn Task>(),
);
6 Likes

I don't know why exactly this happens...but it seems to be documented as working in the exact way you showed it, in the Any comments:

//! `Any` itself can be used to get a `TypeId`, and has more features when used
//! as a trait object. As `&dyn Any` (a borrowed trait object), it has the `is`
//! and `downcast_ref` methods, to test if the contained value is of a given type,
//! and to get a reference to the inner value as a type. As `&mut dyn Any`, there
//! is also the `downcast_mut` method, for getting a mutable reference to the
//! inner value. `Box<dyn Any>` adds the `downcast` method, which attempts to
//! convert to a `Box<T>`. See the [`Box`] documentation for the full details.
//!
//! Note that `&dyn Any` is limited to testing whether a value is of a specified
//! concrete type, and cannot be used to test whether a type implements a trait.
//!
//! [`Box`]: ../../std/boxed/struct.Box.html
//!
//! # Smart pointers and `dyn Any`
//!
//! One piece of behavior to keep in mind when using `Any` as a trait object,
//! especially with types like `Box<dyn Any>` or `Arc<dyn Any>`, is that simply
//! calling `.type_id()` on the value will produce the `TypeId` of the
//! *container*, not the underlying trait object. This can be avoided by
//! converting the smart pointer into a `&dyn Any` instead, which will return
//! the object's `TypeId`. For example:
//!
//! ```
//! use std::any::{Any, TypeId};
//!
//! let boxed: Box<dyn Any> = Box::new(3_i32);
//!
//! // You're more likely to want this:
//! let actual_id = (&*boxed).type_id();
//! // ... than this:
//! let boxed_id = boxed.type_id();
//!
//! assert_eq!(actual_id, TypeId::of::<i32>());
//! assert_eq!(boxed_id, TypeId::of::<Box<dyn Any>>());
//! ```

This behavior has to do with how trait objects implement their own traits. The Any trait is very simple in its definition:

pub trait Any: 'static {
    fn type_id(&self) -> TypeId;
}

impl<T: 'static + ?Sized> Any for T {
    fn type_id(&self) -> TypeId {
        TypeId::of::<T>()
    }
}

Suppose we have a variable x: &dyn Any. Why does Any::type_id(x) (or equivalently, x.type_id()) return the original type_id, rather than TypeId::of::<dyn Any>(), as the impl block would suggest?

The answer is that the impl block applies to every 'static + ?Sized type except for dyn Any, which has its own special implementation. Indeed, if we try to directly implement a trait on its own trait object type, the compiler gives an error (Rust Playground):

pub trait Trait {}
impl Trait for dyn Trait {}
error[E0371]: the object type `(dyn Trait + 'static)` automatically implements the trait `Trait`
 --> src/lib.rs:2:1
  |
2 | impl Trait for dyn Trait {}
  | ^^^^^^^^^^^^^^^^^^^^^^^^ `(dyn Trait + 'static)` automatically implements trait `Trait`

For more information about this error, try `rustc --explain E0371`.

Thus, despite the single blanket impl, there are really two things that can happen when we call Any::type_id() on a reference:

  • If the reference is a &dyn Any, the compiler looks into its vtable to find the original type's type_id() impl.
  • Otherwise, the compiler uses the blanket impl.

This explains the behavior with (&*tasks_b[0]).type_id() vs. (&*tasks_c[0]).type_id(). In the first, we call Any::type_id() on a &dyn Any, which delegates to <SomeTask as Any>::type_id(), producing TypeId::of::<SomeTask>(). In the second, we call Any::type_id() on a &dyn Task, which directly uses the blanket impl <dyn Task as Any>::type_id() to produce TypeId::of::<dyn Task>().

3 Likes

Box's TypeId is not what I meant for - but what if I do this? (I might be going too far with this :stuck_out_tongue: )

(&tasks_c.leak()[0].deref() as &dyn Any).type_id()

What is the resulting type?

1 Like

Going down the line:

tasks_c: Vec<Box<dyn Task>>
tasks_c.leak(): &'static mut [Box<dyn Task>]
tasks_c.leak()[0]: &'static mut Box<dyn Task>
tasks_c.leak()[0].deref(): &'static dyn Task
&tasks_c.leak()[0].deref(): &&'static dyn Task
&tasks_c.leak()[0].deref() as &dyn Any: &dyn Any {&'static dyn Task}
(&tasks_c.leak()[0].deref() as &dyn Any).type_id(): TypeId {&'static dyn Task}

It will result in TypeId::of::<&'static dyn Task>().

1 Like

If you have a Box<dyn Trait> there is no way to get the original type's TypeId unless Trait is a super-trait of Any (i.e. trait Trait: Any { ... }).

One pattern you see all the time when a trait is meant to be used with downcasting is to add an as_any() method (possibly via some utility which is implemented for all types).

You are probably looking for something like this:

use std::{any::Any, fmt::Display};

// Note: I've just added the Display requirement so we can print things
trait Foo: AsAny + Display {}

impl Foo for i32 {}
impl Foo for String {}

fn main() {
    let items = vec![
        Box::new(42) as Box<dyn Foo>,
        Box::new("Hello World".to_string()) as Box<dyn Foo>,
    ];

    for item in items {
        let item = &*item; // Note: dereference the &Box<dyn Foo> to get a &dyn Foo
        println!("{item}:");
        println!("  is i32: {}", item.as_any().is::<i32>());
        println!("  is String: {}", item.as_any().is::<String>());
    }
}

/// A helper trait which lets us get any value as a `&dyn Any`.
trait AsAny {
    fn as_any(&self) -> &dyn Any;
}

impl<A: Any> AsAny for A {
    fn as_any(&self) -> &dyn Any {
        &*self
    }
}

(playground)

Which generates the following output:

42:
  is i32: true
  is String: false
Hello World:
  is i32: false
  is String: true

By the way, leak() is almost never what you want.

It will deliberately leak memory so you can get a &'static reference to the object inside the Box. Most people use this a) as a crutch because they are having trouble with lifetimes and 'static looks like it works, or b) when the compiler says "X doesn't satisfy the 'static lifetime" and look for a method to convert a value into a &'static reference, when actually T: 'static means "this object doesn't contain any references and you can hang onto it as long as you want without worrying about the borrow checker"[1].


  1. This is a bit of a simplification to make it easier to understand. If you are reading this and know the inaccuracies I've glossed over, please don't "Um ackchyually" me because it's not helpful. ↩ī¸Ž

1 Like

Thank you @LegionMammal978 and @Michael-F-Bryan for the insights on Any, trait object TypeId, and 'static lifetimes!