When Generics meet Trait Objects of the same Trait Bound
I'm new to rust, and I'm stuck on a problem related to the implementation of a graph data structure. I'm also new to non-GC programming, so please forgive my ignorance. I've worked through most of The Book at this point, and I wanted to set out to make a real application.
Goal
I have a struct containing a generic list with a trait bound, which can only store objects of the same type because generics can only resolve to a single concrete type,
struct ComponentList<T>( HashMap<T: IsComponent> )
...that I want to gather into a manager list that can accept objects of different types which share the same trait using a trait object.
struct ComponentManager( HashMap<dyn IsComponentList> )
The final goal would be a ComponentManager
that owns a bunch of ComponentList
s, each with different concrete (IsComponent
) types.
Issue
The issue occurs when I try to use this ComponentManager
to manage the ComponentLists. I want to be able to add a component using ComponentManager::add_component()
which will figure out which list to stick the component in based on its concrete type.
cm = ComponentManager::new();
some_component = SomeComponentType::new(); // Must implement IsComponent
// Stick this component in a ComponentList<SomeComponentType>
// Which is stored inside the ComponentManager
cm.add_component(some_component);
However I'm unable to use ComponentManager( HashMap<dyn IsComponentList> )
after I implement IsComponentList::add_component()
. I receive these errors
"the trait ecs::IsComponentList
cannot be made into an object"
"method add_component
has generic type parameters"
Which seem to stem from the fact that add_component()
uses the IsComponent trait bound.
pub trait IsComponentList {
fn add_component(&mut self, component: impl IsComponent);
...
}
Attempted Solution
The errors lead me to this thread: Trait object with generic parameter - #2 by hashedone, suggesting I change the impl IsComponent
to &dyn IsComponent
. This results in even more issues, because I have to now swap out every instance I use the generic T:IsComponent
with the trait object. I don't want this though - I want every component in a ComponentList
to be of the same concrete type!
It feels like there must be a better way to accomplish the goal I've outlined in the first section. Ultimately, what I'm trying to achieve is a way to manage many objects of differing type in a memory efficient way. The problem seems to stem from the fact that my lists are defined with generics, and the list manager needs to use a trait object so it can store these many different types in the same list - but trait objects and generics are not compatible. I've tried restructuring my code a few times, and I've even restarted from scratch trying to approach from another angle.
Am I missing a more idiomatic solution? I know there are crates out there that accomplish this task, but I'm trying to learn why my approach isn't working, and how I need to update my mental model to accomplish similar tasks in the future - I want to understand!
Thank you!