What are its disadvantages?
The main disadvantage is that, when you access a value from a collection of &Any
, the only thing you can do with it is downcast it to a specific, known type. If there is a type you don't know about, then the values of that type are completely opaque: you can literally do nothing with them except count how many there are. If you know the concrete type then you can downcast to it, but you would need to try each possible type that it could be.
Here's an example:
let a = 1u32;
let b = 2.0f32;
let c = "hello";
let v: Vec<&dyn Any> = vec![&a, &b, &c];
match v[0].downcast_ref::<u32>() {
Some(x) => println!("u32: {:?}", x),
None => println!("Not a u32!"),
}
Notice that I had to explicitly downcast to a u32
. Using this approach would involve a logical branch for every possible concrete type, and no compiler warnings if I had forgotten a case.
Trait objects are more versatile because you don't need to know the concrete types in order to use the values - as long as you stick to only using methods of the trait.
For example:
let v: Vec<&dyn Debug> = vec![&a, &b, &c];
println!("u32: {:?}", v[0]); // 1
println!("u32: {:?}", v[1]); // 2.0
println!("u32: {:?}", v[2]); // "hello"
I was able to use all of the values without knowing their concrete types, because I only used the fact that they implement Debug
.
Both of those approaches have a downside over using a concrete type in a homogeneous collection: everything is hidden behind pointers. Accessing the data is always indirect, and that data could end up being spread around in memory, making it much less efficient to access and harder for the compiler to optimize.
Making the collection homogeneous with an enum looks like this:
enum Item<'a> {
U32(u32),
F32(f32),
Str(&'a str),
}
let v: Vec<Item> = vec![Item::U32(a), Item::F32(b), Item::Str(c)];
match v[0] {
Item::U32(x) => println!("u32: {:?}", x),
Item::F32(x) => println!("u32: {:?}", x),
Item::Str(x) => println!("u32: {:?}", x),
}
Here, I still have to know all of the types, but at least there would be a compiler warning if I missed one. Also notice that the enum can own its values, so (apart from the &str
in this case) the data can be tightly packed in memory, making it faster to access.
In summary, Any
is rarely the right answer for a heterogeneous collection, but both trait objects and enums have their own trade-offs.