Here's the scenario, let's assume we have a trait Thing
and in turn a &dyn Thing
, maybe even a Vec<Box<dyn Thing>>
. Now we want to turn that &dyn Thing
into Any
, such that it can be downcast()
ed into some concrete type T
.
trait Thing {
// ...
}
struct Foo;
struct Bar;
impl Thing for Foo {
// ...
}
impl Thing for Bar {
// ...
}
fn maybe_foo(thing: &dyn Thing) {
if /* `thing` is `Foo` */ {
let foo: &Foo = /* `thing` */;
}
}
A Solution
This is where the magic of Any
comes in. In other words, the "straightforward" solution, is to add an as_any()
method to Thing
:
use std::any::Any;
trait Thing {
fn as_any(&self) -> &dyn Any;
}
struct Foo;
struct Bar;
impl Thing for Foo {
fn as_any(&self) -> &dyn Any {
self
}
}
impl Thing for Bar {
fn as_any(&self) -> &dyn Any {
self
}
}
fn maybe_foo(thing: &dyn Thing) {
if let Some(foo) = thing.as_any().downcast_ref::<Foo>() {
// `thing` is `Foo`
} else {
// `thing` is not `Foo`
}
}
However...
There is two annoyances with the above solution:
- The short and simple
as_any()
method, needs to be copy-pasted between all types implementingThing
- If
Thing
is already implemented for many types, then it might be less desirable, to have to manually add anas_any()
method everywhere
Initial Hunch ~ Default Implementations
The initial hunch might be to add a default implementation of as_any()
to Thing
:
trait Thing {
fn as_any(&self) -> &dyn Any {
self
}
}
Nice and easy! But wait... That doesn't seem to compile... Hmm...
error[E0277]: the size for values of type `Self` cannot be known at compilation time
--> src/main.rs:5:9
|
5 | self
| ^^^^ doesn't have a size known at compile-time
|
= note: required for the cast from `&Self` to `&(dyn Any + 'static)`
help: consider borrowing the value, since `&&Self` can be coerced into `&(dyn Any + 'static)`
|
5 | &self
| +
help: consider further restricting `Self`
|
4 | fn as_any(&self) -> &dyn Any where Self: Sized {
| +++++++++++++++++
For more information about this error, try `rustc --explain E0277`.
What's going on?
So, long story short. Since there are no trait bounds constraining Thing
, then self
could both be sized or have an unknown size (i.e. dynamically sized). This is an issue, since the default implementation needs to apply to everything implementing Thing
.
Next Hunch ~ Sized
The next hunch might be to follow the suggestion from the compiler. In other words, adding a Sized
(ref) constraint to Thing
.
Additionally, since Any
has a 'static
lifetime constraint, then it needs to be added to Thing
as well.
trait Thing: Sized + 'static {
fn as_any(&self) -> &dyn Any {
self
}
}
Requiring both Sized
and a 'static
lifetime, might seem reasonable, considering we have a &dyn Thing
or Vec<Box<dyn Thing>>
.
However, attempting to compile, and it immediately becomes clear, why this is not the solution either:
error[E0038]: the trait `Thing` cannot be made into an object
--> src/main.rs:24:22
|
24 | fn maybe_foo(thing: &dyn Thing) {
| ^^^^^^^^^ `Thing` cannot be made into an object
|
note: for a trait to be "object safe" it needs to allow building a vtable to allow the call to be resolvable dynamically; for more information visit <https://doc.rust-lang.org/reference/items/traits.html#object-safety>
--> src/main.rs:3:14
|
3 | trait Thing: Sized + 'static {
| ----- ^^^^^ ...because it requires `Self: Sized`
| |
| this trait cannot be made into an object...
= help: the following types implement the trait, consider defining an enum where each variant holds one of these types, implementing `Thing` for this new enum and using it instead:
Bar
Foo
So what's going on this time?
We want to operate on a &dyn Thing
, i.e. a reference to a trait object. Trait objects are dynamically sized, in other words they could be of any size. While Sized
requires a known fixed/constant size. Thereby requiring Sized
makes it impossible to use the dyn Thing
trait object.
So what do we do?
Actual Solution
We can resolve the issue by introducing a second trait AsAny
. This is then the trait that contains the fn as_any()
method (and fn as_any_mut()
method). Now, instead of having to manually implement AsAny
for all types, then we can automatically implement it through a blanket implementation.
To be able to still use as_any()
through our &dyn Thing
, then we simply need to add AsAny
as a requirement to Thing
.
trait Thing: AsAny {
// ...
}
trait AsAny {
fn as_any(&self) -> &dyn Any;
fn as_any_mut(&mut self) -> &mut dyn Any;
}
impl<T> AsAny for T
where
T: 'static,
{
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
}
Now we get the best of both words. We have an as_any()
method available, without needing to update all impl Thing for XYZ
. The only change needed to trait Thing
is adding the AsAny
requirement.
All in all, now the following works perfectly fine:
fn maybe_foo(thing: &dyn Thing) {
if let Some(foo) = thing.as_any().downcast_ref::<Foo>() {
println!("Foo");
} else {
println!("Not Foo");
}
}
fn main() {
maybe_foo(&Foo);
maybe_foo(&Bar);
let things: Vec<Box<dyn Thing>> = vec![
Box::new(Foo),
Box::new(Bar),
];
for thing in &things {
maybe_foo(thing.as_ref());
}
}
Outputting the following:
Foo
Not Foo
Foo
Not Foo