Come posso creare una collezione eterogenea di oggetti?

Voglio usare gli oggetti tratto in un Vec . In C ++ potrei creare una class di base Thing da cui derivano Monster1 e Monster2 . Potrei quindi creare uno std::vector . Thing oggetti Thing devono memorizzare alcuni dati es. x : int, y : int , ma le classi derivate devono aggiungere più dati.

Attualmente ho qualcosa di simile

 struct Level { // some stuff here pub things: Vec<Box>, } struct ThingRecord { x: i32, y: i32, } struct Monster1 { thing_record: ThingRecord, num_arrows: i32, } struct Monster2 { thing_record: ThingRecord, num_fireballs: i32, } 

Definisco un ThingTrait con i metodi per get_thing_record() , attack() , make_noise() ecc. E li implemento per Monster1 e Monster2 .

Oggetti tratti

Il modo più estensibile per implementare una raccolta eterogenea (in questo caso un vettore) di oggetti è esattamente quello che hai:

 Vec> 

Anche se ci sono momentjs in cui potresti volere una vita che non è 'static , quindi avresti bisogno di qualcosa del tipo:

 Vec> 

Potresti anche avere una raccolta di riferimenti ai tratti, anziché i tratti inscatolati:

 Vec< &ThingTrait> 

Un esempio:

 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> = vec![Box::new(m1), Box::new(m2)]; } 

Box , Rc , &SomeTrait , ecc. Sono tutti oggetti tratti . Questi permettono l’implementazione del tratto su un numero infinito di tipi, ma il compromesso è che richiede una certa quantità di indirezione e invio dinamico.

Guarda anche:

  • Cosa rende qualcosa un “object tratto”?

Enums

Come menzionato nei commenti, se hai un numero fisso di alternative conosciute, una soluzione meno aperta è usare un enum. Ciò non richiede che i valori siano Box ed, ma avrà ancora una piccola quantità di dispatch dinamico per decidere quale variante di enum concreta è presente in 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)]; }