The short answer is because traits are not interfaces.
The long answer is because a &Base
trait object and a &Derived
trait object are not the same thing. The vtables are different because Derived
and Base
are different traits. The vtable for Derived
would include all of Dervied
's methods as well as all of Base
's while the vtable for &Base
would only include Base
's methods.
Now, obviously, Base
's methods are in &Derived
's vtable. So perhaps you could do something clever and get the behavior you want:
If the Base
's methods were listed first in &Derived
's vtable, then you could just cast &Derived
to &Base
and that would work. However, &Derived
and &Base
vtables have different lengths and doing so would chop off everything past the end of &Base
. So if you try to call a method on that object which is part of Derived
, you'll invoke undefined behavior.
You could run some magic code which would analyze the definitions of &Base
and &Derived
and be able to construct a vtable for &Base
from &Derived
. This would require additional information at runtime about these types and their layout. This would also have a non-zero performance cost in addition to the additional memory usage. One of the basic principles of Rust is "zero cost abstractions" which generally means that potentially expensive operations are explicit and not implicit (if let a: Box<Base> = b;
did this, it would generally be considered too implicit).
It's difficult to say in general what a better pattern is. If you are modeling a closed-set of items, enums are generally a better way to go:
enum Animal {
Dog { name: String, age: u8 },
Cat { name: String, age: u8, sleeping: bool },
Fish { name: String, age: u8, in_ocean: bool },
}
If you are trying to do something more complicated, Entity Component Systems like specs
can give you a lot more flexibility than a simple enum.