I'm sorry for the bad title, I have no clue how to express the issue in a single line
Recently, I was making an implementation for the visitor pattern in rust, and I wanted to use generic traits for each different node instead of just having a huge trait with a bunch of "visit_expr" and "visit_operation" methods.
I wanted to express the following relationship:
// A trait that says that a type can traverse a family of nodes
trait Visitor<F> where F : NodeFamily {
type Output;
}
// A trait for implementing the visiting logic for each different node
trait Visit<N> : Visitor<N::Family> where N : Node {
fn visit(&mut self, node: &N) -> Self::Output;
}
// A trait to be implemented on each node for declaring which family it belongs to
trait Node {
type Family: NodeFamily;
}
// A type that implements this node would group different nodes together;
// likely to be implemented on an enum or a type containing Box<dyn Node<Family = Self>>...
trait NodeFamily : Sized {
// this function would be responsible for routing the visitor to the proper nodes
fn accept<V>(&self, v: &mut V) -> <V as Visitor<Self>>::Output
where
V : Visitor<Self>,
// here comes the tricky bit, we also want V to know how to visit every possible node that belongs to our family:
for<N : Node<Family = Self>> V : Visit<N>;
}
I am aware that last bit isn't rust code, but I based it off the for<>
syntax that we have for lifetimes, I don't believe that today in rust we can express that relationship, but what would be a proper, or idiomatic, way of doing this?
Is there a precedent for semantics like this in other languages? And could something like this eventually come to rust?
One problem I realized that this could lead to is: an external crate could declare a new type that implements node for a family it doesn't own, and this could break external code that has a visitor for that family since there would be no Visit implementation for that new node.
I was able to solve my problem by moving the generic Visitor to the trait level rather than the function, which allows us to put trait bounds on the impl blocks, but it feels hacky (so much that I made a macro that does that since it becomes a lot of boilerplate) Here's the macro line that ensures this relationship of V must implement Visit for all N that implements Node<Family = Self>: visita/node_group.rs at main · jvcmarcenes/visita · GitHub (the naming is a bit different and the crate isn't documented yet, but it shows what I'm talking about)