Hi, I'm in need of some design help. I'm trying to have a collection of RuntimeModule
s which are of different types. I'm using trait objects to store in a manager Modules
:
pub trait RuntimeModule {
fn tick(&mut self, modules: &Modules, delta: f32);
}
pub struct Modules {
modules: Vec<RefCell<Box<RuntimeModule>>>
}
impl Modules {
//omitted for brevity...
}
struct Foo {}
impl RuntimeModule for Foo {
fn tick(&mut self, modules: &Modules, delta: f32) { // do something }
}
struct Bar {}
impl RuntimeModule for Bar {
fn tick(&mut self, modules: &Modules, delta: f32) { // do something }
}
fn main() {
let mut modules = Modules::new();
modules.add(Box::new(RefCell::new(Foo::new())), "Foo");
modules.add(Box::new(RefCell::new(Bar::new())), "Bar");
}
So far so good. In order for the modules to communicate with each other I would like each type to be able to expose a data type, that the owner can write to (on it's tick) and other modules can read during their update. For the Foo
type that would be something like:
struct FooData {
somedata: i64
}
It could either be stored as a member in Foo
or externally in the Modules
manager. The other modules would retrieve using something like:
impl RuntimeModule for Bar {
fn tick(&mut self, delta: f32) {
let foodata = modules.get_data<FooData>("Foo");
// use foodata for something interesting
}
}
This is where I'm running into problem. Due to type erasure I only have access to RuntimeModule
, and since it's used as a trait object it's not possible to declare methods with generic type parameters.
I tried making an additional trait to access a modules data:
pub trait RuntimeModuleWithData {
type Data;
fn get_data(&self) -> Option<Self::Data>;
}
However, how do I cast from a RuntimeModule
to RuntimeModuleWithData
pub fn get_data<M:RuntimeModuleWithData>(&self, identifier: &str) -> Option<M::Data> {
// Find the module with the correct identifier
let module: &RefCell<Box<RuntimeModule>> = self.find_entry(identifier);
// Cast to RuntimeModuleWithData and call get_data?
}
I understand why Rust doesn't allow this, there are no contract that ensures all the modules implement the RuntimeModuleWithData
trait. I can't add it to trait object, only auto traits are allowed as additional trait bounds. Is there a way of doing the type coercion without using unsafe?
As I understand it, I could use a boxed Any
instead of RuntimeModule
, but it would require a 'static
lifetime, and I would rather like to avoid that.
I'm totally open to suggestions of completely different designs, I'm trying my best to get rid of my C++ ways