Как создать гетерогенную коллекцию объектов?

Я хочу использовать объекты-объекты в Vec. В С++ я мог бы создать базовый класс Thing, из которого выведены Monster1 и Monster2. Тогда я мог бы создать std::vector<Thing*>. Thing объекты должны хранить некоторые данные, например. x : int, y : int, но производным классам необходимо добавить больше данных.

В настоящее время у меня есть что-то вроде

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,
}

Я определяю a ThingTrait с методами для get_thing_record(), attack(), make_noise() и т.д. и реализую их для Monster1 и Monster2.

Ответ 1

Черты объектов

Самый расширяемый способ реализации гетерогенной коллекции (в данном случае вектора) объектов - это именно то, что у вас есть:

Vec<Box<dyn ThingTrait + 'static>>

Хотя бывают моменты, когда вам может потребоваться не 'static, поэтому вам нужно что-то вроде:

Vec<Box<dyn ThingTrait + 'a>>

Вы также можете иметь коллекцию ссылок на черты, а не в штучной упаковке:

Vec<&dyn ThingTrait>

Пример:

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<SomeTrait>, Rc<SomeTrait>, &SomeTrait и т.д. Являются объектами признаков. Они позволяют реализовать эту черту для бесконечного числа типов, но компромисс заключается в том, что она требует некоторой косвенности и динамического распределения.

Смотрите также:

Перечисления

Как упоминалось в комментариях, если у вас есть фиксированное количество известных альтернатив, менее открытым решением является использование enum. Это не требует, чтобы значения были Box ed, но он все равно будет иметь небольшую динамическую диспетчеризацию, чтобы решить, какой конкретный вариант enum присутствует во время выполнения:

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)];
}