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(),
);
}
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().
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:
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 fordyn 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>().
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
}
}
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].
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. âŠī¸