user4063815
user4063815

Reputation:

Rust subtyping/"runtime polymorphism"/using traits in Boxes

Consider the following dumbed-down example:

pub trait ThemePark<A, V>
where
    A: Attraction,
    V: Visitor,
{
    fn create(square_size: u32, name: &str) -> Self;
    fn get_attractions(&self) -> Vec<A>;
    fn get_visitors(&self) -> Vec<V>;
}

pub trait Attraction {}
pub trait Visitor {}

A concrete ThemePark could have an arbitrary set of attractions, as well as visitors. They are implemented with structs:

struct ElderlyWhiteMale;
impl Visitor for ElderlyWhiteMale {}

A ThemePark is wrapped in some company's assets, like so:

pub struct Asset<'a> {
    name: &str,
    theme_park: Box<ThemePark<> + 'a> // <-- This won't compile, as ThemePark needs 2 type arguments
}

This begins my pain. I put ThemePark in a Box because I don't know the size of it at compile time. It could be wrapped around any kind of Attraction and Visitor.

ThemePark needs 2 type arguments, but I can't know them at compile-time. Somewhere in my code I read this from an external file and build a ThemePark accordingly.

The idea is, that at runtime I can create a ThemePark from an external source and then invoke the functions defined in the trait on it.

impl Asset {
    fn init_and_query() -> () {
        let theme_park: Box<ThemePark> = match external_file.get_theme_park_type {
            ThemeParkType::FUN => unimplemented! {"There is no fun, yet!"},
            ThemeParkType::SERIOUS => {
                println!("Creating serious themepark");

                SeriousThemePark::create(size /*...*/)
            }
        };

        let attractions = theme_park.get_attractions();
        // ... Do something with the attractions
    }
}

pub enum ThemeParkType {
    FUN,
    SERIOUS,
}

I understand that I can't put the ThemePark as-is on the stack... it's size is unknown at compile time, so the compiler can't know what to allocate.

That's why I either use a reference & or wrap it in a Box like I do here.

I understand there is type erasure, meaning that I would get back only a ThemePark and not a SeriousThemePark, but that would suffice for the moment.

Am I using traits all wrong here? How would you go and fix that. Coming from Java/Scala/C++ I seem to be stuck too deep in existing thinking.

Upvotes: 1

Views: 1878

Answers (1)

Matthieu M.
Matthieu M.

Reputation: 299930

Rust polymorphism is very similar to C++ polymorphism in that regard; it features:

  • compile-time polymorphism, to parameterize an item with types known at compile-time,
  • run-time polymorphism, when the concrete types are not known at compile-time.

Rust uses trait to define an interface which is then used to both constrain compile-time type parameters and serve as base-class/interface for run-time polymorphism, which is perhaps where your confusion comes from, however both kinds of polymorphism are inherently different.

pub struct Asset<'a> {
    name: &str,
    theme_park: Box<ThemePark<> + 'a> // <-- This won't compile, as ThemePark needs 2 type arguments
}

Then you should NOT be using compile-time polymorphism, and instead define ThemePark as:

pub trait ThemePark {
    fn create(square_size: u32, name: &str) -> Self;
    fn get_attractions(&self) -> Vec<Box<Attraction>>;
    fn get_visitors(&self) -> Vec<Box<Visitor>>;
}

By instantiating a ThemePark<A, V> you create a theme park which can only ever contain one type of attractions (it's all Haunted Houses here, no Flume Ride sorry!) and one type of visitors (only Elderly Guys in, no Elderly Ladies or Kids).

Upvotes: 4

Related Questions