But to access it we have to already know about the real data type

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 syntax is a little confusing, since it's backwards from what we would write in a use statement, but it otherwise looks similar. It does read well, though: if step is forward almost works as a sentence.

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.

Some and None are the two possible values of the Option enumeration, which is another thing included in the prelude. It is used widely to represent data values that might or might not exist, especially when they are not required to exist. It's common in other languages to have a special value such as NULL, null, None, or nil, which can be assigned to anything. Rust's None can only be assigned to Option, which helps the compiler ensure that everything is correct.

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.

In addition to downcast_ref, the Any trait also provides downcast_mut, which gives us a mutable reference. In some circumstances, a downcast function is also available, which moves the value into our current scope instead of borrowing it.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.123.147