Boilerplate for building visitors, inspired by
derive-visitor.
Driving a visitor
The premise of this crate is that to visit a type means to call a function on each of its
fields. The Visit[Mut] and Drive[Mut] traits of this module provide the simplest interface
for this: a type that implements a Visit<...> for a bunch of types is like a bundle of
FnMut closures, and drive_inner on a type T calls <V as Visit<FieldTy>>::visit on each
field of T.
The Drive/DriveMut derive macros implement these types automatically for a type. With that
boilerplate out of the way, it becomes easy to define flexible visitors.
The output of the derive macros looks like:
#[derive(Drive)]
enum MyList {
Empty,
Cons(String, Box<MyList>),
}
# use derive_generic_visitor::{Drive, Visitor, Visit};
# use std::ops::ControlFlow;
# enum MyList {
# Empty,
# Cons(String, Box<MyList>),
# }
impl<'s, V> Drive<'s, V> for MyList
where
V: Visitor,
V: Visit<'s, String>,
V: Visit<'s, Box<MyList>>,
{
fn drive_inner(&'s self, v: &mut V) -> ControlFlow<V::Break> {
match self {
Self::Empty => {}
Self::Cons(x, y) => {
v.visit(x)?;
v.visit(y)?;
}
}
ControlFlow::Continue(())
}
}
As you can see, this is not recursive in any way: x.drive_inner(v) simply calls v.visit()
on each field of x; it is up to the visitor to recurse into nested structures if it wishes.
Defining useful visitors
A visitor is a type that implements Visit<T>/VisitMut<T> for a set of types T. An
implementation of Visit[Mut] typically involves calling x.drive_inner(self) to recurse into
the type's contents, with some work done before or after that call. The Visit and VisitMut
derive macros make such usage straightforward.
# use derive_generic_visitor::*;
#[derive(Drive)]
enum MyList {
Empty,
Cons(MyNode),
}
#[derive(Drive)]
struct MyNode {
val: String,
next: Box<MyList>
}
#[derive(Default, Visitor, Visit)]
#[visit(drive(MyList))] #[visit(drive(for<T> Box<T>))] #[visit(enter(MyNode))] #[visit(skip(String))] struct ConcatVisitor(String);
impl ConcatVisitor {
fn enter_my_node(&mut self, node: &MyNode) {
self.0 += &node.val;
}
}
pub fn concat_list(x: &MyList) -> String {
ConcatVisitor::default().visit_by_val_infallible(x).0
}
This expands to:
# use derive_generic_visitor::*;
# #[derive(Drive)]
# enum MyList {
# Empty,
# Cons(MyNode),
# }
# #[derive(Drive)]
# struct MyNode {
# val: String,
# next: Box<MyList>
# }
# impl ConcatVisitor {
# fn enter_my_node(&mut self, node: &MyNode) {
# self.0 += &node.val;
# }
# }
#[derive(Default)]
struct ConcatVisitor(String);
impl Visitor for ConcatVisitor {
type Break = Infallible;
}
impl<'s> Visit<'s, MyList> for ConcatVisitor {
fn visit(&mut self, x: &'s MyList) -> ControlFlow<Self::Break> {
x.drive_inner(self)
}
}
impl<'s, T> Visit<'s, Box<T>> for ConcatVisitor
where
Self: Visit<'s, T>,
{
fn visit(&mut self, x: &'s Box<T>) -> ControlFlow<Self::Break> {
x.drive_inner(self)
}
}
impl<'s> Visit<'s, MyNode> for ConcatVisitor {
fn visit(&mut self, x: &'s MyNode) -> ControlFlow<Self::Break> {
self.enter_my_node(x);
x.drive_inner(self)?;
ControlFlow::Continue(())
}
}
impl<'s> Visit<'s, String> for ConcatVisitor {
fn visit(&mut self, x: &'s String) -> ControlFlow<Self::Break> {
ControlFlow::Continue(())
}
}
The options available are:
enter(Ty): call self.enter_ty(x) before recursing with drive_inner.
exit(Ty): call self.exit_ty(x) after recursing with drive_inner.
override(Ty): call self.visit_ty(x)?, which may or may not recurse if it wishes to.
drive(Ty): recurse with drive_inner.
skip(Ty): do nothing.
Ty: alias for override(Ty)
Instead of Ty, one can always write for<A, B, C> Ty<A, B, C> to make a generic impl. For
enter, exit and override, one may also write name: Ty so that visit_name etc is
called instead of visit_ty.
Reusable visitors
For more complex scenarios where one-off visitor structs would be tedious, this crate provides
a final macro: visitable_group. Given a set of types of interest, this generates a pair of
traits: a Visitable trait implemented by all these types, and a Visitor trait with default
methods that defines visitors over these types.
This is a reusable version of the one-off visitor structs we saw in the previous section: the
enter_foo/exit_foo/visit_foo methods are now trait methods, in such a way that many
visitors can be defined for that same set of types.
# use derive_generic_visitor::*;
#[derive(Drive)]
enum List {
Empty,
Cons(Node),
}
#[derive(Drive)]
struct Node {
val: String,
next: Box<List>
}
#[visitable_group(
visitor(drive_list(&ListVisitor)), // also available: `&mut`
drive(List, for<T: ListVisitable> Box<T>),
skip(String),
override(Node),
)]
trait ListVisitable {}
#[derive(Visitor)]
struct SomeVisitor;
impl ListVisitor for SomeVisitor {
}
The generated visitor trait has methods much like those from the Visit[Mut] derives, that can
be overriden freely. The result is:
# use derive_generic_visitor::*;
# #[derive(Drive)]
# enum List {
# Empty,
# Cons(Node),
# }
# #[derive(Drive)]
# struct Node {
# val: String,
# next: Box<List>
# }
#[repr(transparent)]
pub struct ListVisitableWrapper<V: ?Sized>(V);
impl<V: ?Sized> ListVisitableWrapper<V> {
fn wrap(x: &mut V) -> &mut Self {
unsafe { std::mem::transmute(x) }
}
}
impl<V: Visitor> Visitor for ListVisitableWrapper<V> {
type Break = V::Break;
}
impl<'s, V: ListVisitor, T: ListVisitable> Visit<'s, T> for ListVisitableWrapper<V> {
fn visit(&mut self, x: &'s T) -> ControlFlow<Self::Break> {
self.0.visit(x)
}
}
trait ListVisitable {
fn drive_list<V: ListVisitor>(&self, v: &mut V) -> ControlFlow<V::Break>;
}
trait ListVisitor: Visitor + Sized {
fn visit<'a, T: ListVisitable>(
&'a mut self,
x: &T,
) -> ControlFlow<Self::Break> {
x.drive_list(self)
}
fn visit_inner<T>(&mut self, x: &T) -> ControlFlow<Self::Break>
where
T: for<'s> Drive<'s, ListVisitableWrapper<Self>>,
{
x.drive_inner(ListVisitableWrapper::wrap(self))
}
fn visit_node(&mut self, x: &Node) -> ControlFlow<Self::Break> {
self.enter_node(x);
self.visit_inner(x)?;
self.exit_node(x);
Continue(())
}
fn enter_node(&mut self, x: &Node) {}
fn exit_node(&mut self, x: &Node) {}
}
impl ListVisitable for List {
fn drive_list<V: ListVisitor>(&self, v: &mut V) -> ControlFlow<V::Break> {
v.visit_inner(self)
}
}
impl<T: ListVisitable> ListVisitable for Box<T> {
fn drive_list<V: ListVisitor>(&self, v: &mut V) -> ControlFlow<V::Break> {
v.visit_inner(self)
}
}
impl ListVisitable for String {
fn drive_list<V: ListVisitor>(&self, v: &mut V) -> ControlFlow<V::Break> {
ControlFlow::Continue(())
}
}
impl ListVisitable for Node {
fn drive_list<V: ListVisitor>(&self, v: &mut V) -> ControlFlow<V::Break> {
v.visit_node(self)
}
}