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
)
(&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 Like
Thank you @LegionMammal978 and @Michael-F-Bryan for the insights on Any, trait object TypeId, and 'static
lifetimes!