1

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.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • Could you show how you are going to implement some `ThemePark`s, let's say, `impl ThemePark??, ???> for FunIsland { ??? }`? – kennytm Jan 03 '18 at 14:49
  • 1
    You should go back and re-read [*The Rust Programming Language*](https://doc.rust-lang.org/book/second-edition/), specifically the chapter about [*trait objects*](https://doc.rust-lang.org/book/second-edition/ch17-02-trait-objects.html). – Shepmaster Jan 03 '18 at 15:19

1 Answers1

4

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).

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
  • The problem in `Asset` persists though. I cannot have an `Asset` that has a member of `Box` because `ThemePark` cannot be made into an object. Is there any workaround for that? –  Jan 03 '18 at 15:40
  • @Sorona That's probably because `create` has no receiver, right? See: https://stackoverflow.com/questions/38159771/why-can-a-trait-not-construct-itself –  Jan 03 '18 at 15:48
  • Still not a solution to my problem. Of course the individual `Attraction` or `Visitor` are different structs, so an impl of `ThemePark` will create `ElderlyGuys` as Visitors and then I get that `ElderlyGuys` was given but `Visitors` was expected. –  Jan 03 '18 at 16:32
  • @Sorona instead of directly inserting an `ElderlyGuy` into the `Vec`, insert `Box::new(ElderlyGuy::new())` into the `Vec`. – NovaDenizen Jan 03 '18 at 17:10
  • @NovaDenizen I do just that (else I would get an error that a `Box` was expected while an `ElderlyGuy` was given), but then I get that `Box` is not the same as `Box` and the latter was expected! –  Jan 03 '18 at 18:24
  • Maybe you didn't declare the type of the vector. See [here](https://play.rust-lang.org/?gist=62faba37b5f0830cda9133d5530bc560&version=stable), especially the comment in `make_buffet()`. – NovaDenizen Jan 03 '18 at 20:47