Getting back to our example, we'll create our array of driving directions, and we'll add a something else that isn't a driving direction, just to prove that we can.
Here, we have an array of Any trait references containing driving directions and one other thing:
use std::any::Any;
//...
pub struct DoesHaveAnyTrait {
pub name: String,
pub count: i32,
}
//...
let okay = String::from("okay");
let directions: [&dyn Any; 7] = [
&Forward{ blocks: 5 },
&Turn{ slight: true, right: false },
&Forward{ blocks: 1 },
&Turn{ slight: false, right: true },
&Forward{ blocks: 2 },
&Stop{},
&DoesHaveAnyTrait{ name: okay, count: 16},
];
So far, it looks rather similar to our trait object example, which makes sense since Any is a trait too, and this is still an array of trait object references. The huge difference here is that we've added one more item to the array, and it doesn't have anything at all to do with driving directions.
Trait object references only give us access to the trait's functions. Now that we've got an array of Any trait object references, what functions does Any provide that let us do something useful? Any gives us two important functions: there is a function that checks whether the contained value has a particular data type, and there is a family of functions that lets us extract the contained value.
For a first example, we'll look at the function that allows us to check the data type:
for step in directions.iter() {
if step.is::<Forward>() {
println!("Go forward");
}
else if step.is::<Turn>() {
println!("Turn here");
}
else if step.is::<Stop>() {
println!("Stop now");
}
}
There's some new syntax here. When we say step.is::<Forward>(), we're saying that we want to call the is function that was defined within the (automatically created) impl Any for Forward implementation. The compiler knows we're talking about Any because step is an &dyn Any, but it wouldn't know we wanted the Forward version rather than one of the countless other implementations of Any for specific types, so we needed to tell it.
This version is a little unsatisfying, though, because the printout is entirely based on the data type, without taking into account the information stored in the data values. We could do just as well with an unparameterized enumeration. Fortunately, we can also use the downcast functions of Any to get access to the referenced value:
for step in directions.iter() {
if let Some(x) = step.downcast_ref::<Forward>() {
x.forward();
}
else if let Some(x) = step.downcast_ref::<Turn>() {
x.forward();
}
else if let Some(x) = step.downcast_ref::<Stop>() {
x.forward();
}
}
Once again, we're telling Rust that we want to call the versions of the Any functions that come from specific type implementations of that trait. In the first if let branch, we're asking Any to give us a reference to a Forward data value. If the value actually is a Forward data value, that function will return an enumeration value called Some, which has the reference as its parameter. If the value is not a Forward data value, the function will return an enumeration value called None, which naturally does not match the if let pattern.
The x variables in this example are actual references to Forward, Turn, or Stop data values, respectively, and so if we make it into the code block for one of the if branches, we have access to everything that it is possible to do with that data type, not just the features defined by a particular trait. In fact, we're calling the forward functions implemented in the PrintableDirection trait for those types, which is a pretty good demonstration that we have full access.
Notice that, with both is and downcast_ref, there's no way to use them without specifying which concrete data type we're interested in. If we try to use those functions without specifying exactly which data type to use, we get an error like this:
That means that while Any can be used to store almost anything, we can't access the stored information unless we explicitly handle the correct data type for the stored value. In our example, we didn't have an if branch to handle DoesHaveAnyTrait values, so the last value in the array ended up being ignored.