Reputation:
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
Reputation: 299930
Rust polymorphism is very similar to C++ polymorphism in that regard; it features:
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