I have various generic containers defined in many different crates using a common declarative macro. Each looks something like:
struct Container1<T> {
a: T,
b: T,
}
On the other hand, I have a separate crate model
with a trait ModelTree
that I want to implement on all the generic containers, when their type parameters implement some other trait Model
:
trait ModelTree {
fn integrate_model_tree(self, context: Context);
}
Ideally this would be
impl<T: Model> ModelTree for Container1<T> {
...
}
[...and similarly for other containers...]
However, the model
crate cannot depend directly on all the crates that define the containers (there are many), and the crates that define the containers also cannot depend on the model
crate.
To get around this, at a high level I thought to define a common crate common
that defines a trait that is "similar to" ModelTree
, have all the containers (e.g. Container1
) implement this common trait, and then implement ModelTree
generically for anything that implements the common trait. However, I keep getting unconstrained type parameter
whenever I make an attempt to do this.
Here's concretely what I tried to do in common
. This playground link has the full MRE. Define a common visitor trait:
trait Visitor {
type ItemType;
fn visit(&mut self, item: Self::ItemType);
}
Define a "visitable" trait, AcceptVisitor
:
trait AcceptVisitor<V: Visitor<ItemType = Self::ItemType>> {
type ItemType;
fn accept_visitor(self, visitor: V);
}
Implement AcceptVisitor
for all generic containers:
impl<T, V: Visitor<ItemType = T>> AcceptVisitor<V> for Container1<T> {
type ItemType = T;
fn accept_visitor(self, mut visitor: V) {
visitor.visit(self.a);
visitor.visit(self.b);
}
}
Add a visitor shim in the model
crate:
struct ModelVisitor<M: Model> {
context: Context,
phantom: core::marker::PhantomData<M>,
}
impl<M: Model> Visitor for ModelVisitor<M> {
type ItemType = M;
fn visit(&mut self, item: M) {
self.context.add_model(item);
}
}
Finally, implement ModelTree
for all types that implement AcceptVisitor
:
impl<
M: Model,
V: Visitor<ItemType = M>,
A: AcceptVisitor<ModelVisitor<M>, ItemType = M>,
> ModelTree for A
{
fn integrate_model_tree(self, context: Context) {
let visitor = ModelVisitor { context, phantom: core::marker::PhantomData };
self.accept_visitor(visitor);
}
}
This is where I get the unconstrained type parameter
error for both M
and V
.
Is there some way to accomplish my high-level problem statement? Am I going down the wrong path with my concrete attempt, or is there a way to tweak it to make it work? Thanks!
This smells hard like a XY problem, but if that really is what you want to do, you can move the Visitor
generic parameter from the trait to the accept_visitor()
method. This is also more correct, since the implementation doesn't rely on a single visitor.
trait AcceptVisitor {
type ItemType;
fn accept_visitor<V: Visitor<ItemType = Self::ItemType>>(self, visitor: V);
}
impl<T> AcceptVisitor for Container1<T> {
type ItemType = T;
fn accept_visitor<V: Visitor<ItemType = T>>(self, mut visitor: V) {
visitor.visit(self.a);
visitor.visit(self.b);
}
}
impl<M: Model, A: AcceptVisitor<ItemType = M>> ModelTree for A {
fn integrate_model_tree(self, context: Context) {
let visitor = ModelVisitor {
context,
phantom: core::marker::PhantomData,
};
self.accept_visitor(visitor);
}
}