I want to use trait objects in a Vec
. In C++ I could make a base class Thing
from which is derived Monster1
and Monster2
. I could then create a std::vector<Thing*>
. Thing
objects must store some data e.g. x : int, y : int
, but derived classes need to add more data.
Currently I have something like
struct Level {
// some stuff here
pub things: Vec<Box<ThingTrait + 'static>>,
}
struct ThingRecord {
x: i32,
y: i32,
}
struct Monster1 {
thing_record: ThingRecord,
num_arrows: i32,
}
struct Monster2 {
thing_record: ThingRecord,
num_fireballs: i32,
}
I define a ThingTrait
with methods for get_thing_record()
, attack()
, make_noise()
etc. and implement them for Monster1
and Monster2
.
A heterogeneous collection is a datatype that is capable of storing data of different types, while providing operations for look-up, update, iteration, and others. There are various kinds of heterogeneous collections, differing in representation, invariants, and access operations.
Arraylist is a class which implements List interface . It is one of the widely used because of the functionality and flexibility it offers. It is designed to hold heterogeneous collections of objects. The capacity of an ArrayList is the number of elements the ArrayList can hold.
Heterogeneous object is a volumetric object with interior structure where different physically-based attributes are defined, e.g. spatial different material compositions, micro-structures, colour, density, etc.
The most extensible way to implement a heterogeneous collection (in this case a vector) of objects is exactly what you have:
Vec<Box<dyn ThingTrait + 'static>>
Although there are times where you might want a lifetime that's not 'static
, so you'd need something like:
Vec<Box<dyn ThingTrait + 'a>>
You could also have a collection of references to traits, instead of boxed traits:
Vec<&dyn ThingTrait>
An example:
trait ThingTrait {
fn attack(&self);
}
impl ThingTrait for Monster1 {
fn attack(&self) {
println!("monster 1 attacks")
}
}
impl ThingTrait for Monster2 {
fn attack(&self) {
println!("monster 2 attacks")
}
}
fn main() {
let m1 = Monster1 {
thing_record: ThingRecord { x: 42, y: 32 },
num_arrows: 2,
};
let m2 = Monster2 {
thing_record: ThingRecord { x: 42, y: 32 },
num_fireballs: 65,
};
let things: Vec<Box<dyn ThingTrait>> = vec![Box::new(m1), Box::new(m2)];
}
Box<dyn SomeTrait>
, Rc<dyn SomeTrait>
, &dyn SomeTrait
, etc. are all trait objects. These allow implementation of the trait on an infinite number of types, but the tradeoff is that it requires some amount of indirection and dynamic dispatch.
See also:
As mentioned in the comments, if you have a fixed number of known alternatives, a less open-ended solution is to use an enum. This doesn't require that the values be Box
ed, but it will still have a small amount of dynamic dispatch to decide which concrete enum variant is present at runtime:
enum Monster {
One(Monster1),
Two(Monster2),
}
impl Monster {
fn attack(&self) {
match *self {
Monster::One(_) => println!("monster 1 attacks"),
Monster::Two(_) => println!("monster 2 attacks"),
}
}
}
fn main() {
let m1 = Monster1 {
thing_record: ThingRecord { x: 42, y: 32 },
num_arrows: 2,
};
let m2 = Monster2 {
thing_record: ThingRecord { x: 42, y: 32 },
num_fireballs: 65,
};
let things = vec![Monster::One(m1), Monster::Two(m2)];
}
See also:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With