How do I create a heterogeneous collection of objects?

后端 未结 1 2071
忘了有多久
忘了有多久 2020-11-22 09:34

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.

相关标签:
1条回答
  • 2020-11-22 09:53

    Trait objects

    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<SomeTrait>, Rc<SomeTrait>, &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:

    • What makes something a "trait object"?
    • What does "dyn" mean in a type?

    Enums

    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 Boxed, 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)];
    }
    
    0 讨论(0)
提交回复
热议问题