Boilerplate for building visitors, inspired by
derive-visitor.
The basic purpose of this crate is to provide a simple derive macro that does "call a function on each field of this type". This building block can then be used for a ton of things, and I'm hoping to save people work by providing it.
In this crate, "visiting a type" is the name we give to "calling 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<A> + Visit<B> for a bunch of types is like a bundle of FnMut(&mut A), FnMut(&mut B) closures, and <T as Drive>::drive_inner(v) 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>),
}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 to do so.
On top of the Drive/DriveMut derive macros, this crate provide more opinionated utilities to get
simple visitor architectures started.
A visitor is a type that implements Visit<T>/VisitMut<T> for a set of types T. An
implementation of Visit[Mut] typically involves doing some work then calling x.drive_inner(self)
to recurse into the type's contents. The crate provides Visit and VisitMut derive macros to make
such usage straightforward.
#[derive(Drive)]
enum MyList {
Empty,
Cons(MyNode),
}
#[derive(Drive)]
struct MyNode {
val: String,
next: Box<MyList>
}
#[derive(Default, Visitor, Visit)]
#[visit(drive(MyList))] // recurse without custom behavior
#[visit(drive(for<T> Box<T>))] // recurse on a polymorphic type without custom behavior
#[visit(enter(MyNode))] // call `self.enter_my_node` before recursing
#[visit(skip(String))] // do nothing when visiting a string
struct ConcatVisitor(String);
impl ConcatVisitor {
// This will be called when the visitor reaches a `MyNode`, before it recurses.
fn enter_my_node(&mut self, node: &MyNode) {
self.0 += &node.val;
}
}
/// Concatenate all the strings in this list.
pub fn concat_list(x: &MyList) -> String {
ConcatVisitor::default().visit_by_val_infallible(x).0
}This expands to:
#[derive(Default)]
struct ConcatVisitor(String);
impl Visitor for ConcatVisitor {
type Break = Infallible;
}
// Recurse without custom behavior
impl<'s> Visit<'s, MyList> for ConcatVisitor {
fn visit(&mut self, x: &'s MyList) -> ControlFlow<Self::Break> {
x.drive_inner(self)
}
}
// Recurse without custom behavior
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)
}
}
// Call `self.enter_my_node` before recursing
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(())
}
}
// Do nothing on a string
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): callself.enter_ty(x)before recursing withdrive_inner.exit(Ty): callself.exit_ty(x)after recursing withdrive_inner.override(Ty): callself.visit_ty(x)?, which may or may not recurse if it wishes to and can also early-return.drive(Ty): recurse withdrive_inner.skip(Ty): do nothing.Ty: alias foroverride(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 other_name: Ty so that visit_other_name is
called instead of visit_ty.
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.
#[derive(Drive)]
enum List {
Empty,
Cons(Node),
}
#[derive(Drive)]
struct Node {
val: String,
next: Box<List>
}
#[visitable_group(
// Specifies the trait to generate. Also available: `&mut ListVisitor` for mutating visitors.
visitor(drive_list(&ListVisitor)),
drive(List, for<T: ListVisitable> Box<T>), // simply recurse through these, no override available
skip(String), // simply skip these, no override available
override(Node), // define overrideable `enter_node`, `exit_node` and `visit_node` methods
)]
trait ListVisitable {}
#[derive(Visitor)]
struct SomeVisitor;
// The `ListVisitor` trait was generated by the `visitable_group` macro.
// Calling `visitor.visit(&list)` will run the visitor on the list.
impl ListVisitor for SomeVisitor {
// Here, methods `enter_node`, `exit_node` and `visit_node` are available to override.
fn enter_node(&mut self, node: &Node) {
// ...
}
}The generated visitor trait has methods much like those from the Visit[Mut] derives, that can
be overridden freely. The result is:
/// Implementation detail: wrapper that implements `Visit[Mut]<T>` for `T: ListVisitable`,
/// and delegates all the visiting to our trait's `drive[_mut]`. Used in the implementation of
/// `visit_inner`
#[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 {
/// Recursively visit this type with the provided visitor. This calls the visitor's `visit_$any`
/// method if it exists, otherwise `visit_inner`.
fn drive_list<V: ListVisitor>(&self, v: &mut V) -> ControlFlow<V::Break>;
}
trait ListVisitor: Visitor + Sized {
/// Visit a visitable type. This calls the appropriate method of this trait on `x`
/// (`visit_$ty` if it exists, `visit_inner` if not).
fn visit<'a, T: ListVisitable>(
&'a mut self,
x: &T,
) -> ControlFlow<Self::Break> {
x.drive_list(self)
}
/// Visit the contents of `x`. This calls `self.visit()` on each field of `T`. This
/// is available for any `ListVisitable` type whose contents are all `ListVisitable`.
fn visit_inner<T>(&mut self, x: &T) -> ControlFlow<Self::Break>
where
T: for<'s> Drive<'s, ListVisitableWrapper<Self>> + ListVisitable,
{
x.drive_inner(ListVisitableWrapper::wrap(self))
}
/// Overrideable method called when visiting a `$ty`. When overriding this method,
/// call `self.visit_inner(x)` to keep recursively visiting the type, or don't call
/// it if the contents of `x` should not be visited.
///
/// The default implementation calls `enter_$ty` then `visit_inner` then `exit_$ty`.
fn visit_node(&mut self, x: &Node) -> ControlFlow<Self::Break> {
self.enter_node(x);
self.visit_inner(x)?;
self.exit_node(x);
Continue(())
}
/// Called when starting to visit a `$ty` (unless `visit_$ty` is overriden).
fn enter_node(&mut self, x: &Node) {}
/// Called when finished visiting a `$ty` (unless `visit_$ty` is overriden).
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)
}
}To illustrate, the typical visit loop would look like, given a MyVisitor: ListVisitor:
<MyVisitor as ListVisitor>::visit(v, x)// entrypoint<Node as GroupVisitable>::drive_list(x, v)// assumingx: Node<MyVisitor as ListVisitor>::visit_node(v, x)// here lives custom behavior<MyVisitor as ListVisitor>::visit_inner(v, x)// assumingvisit_nodewas not overridden<Node as Drive>::drive_inner(ListVisitorWrapper(v))- calls
<MyVisitor as ListVisitor>::visit(v, &x.field)on each field ofx, completing the loop.
The options available for the visitable_group macro are:
visitor(drive_method_name(&[mut|two]TraitName)[, infallible][, bounds(Bound1 + Bound2)]): derive a visitor trait namedTraitName.- the presence of
mutdetermines whether theTraitNamevisitor will operate on mutable or immutable borrows. - the presence of
twodetermines whether theTraitNamevisitor will operate on a single value or two values at once (see Lockstep section). Lockstep visitors don't support mutability. - the optional
infallibleflag enables an infallible-style interface for the visitor, where its methodsvisit_$tyreturn()instead ofControlFlow<_>. - the optional
bounds(...)adds super trait bounds to the generatedTraitNametrait.
- the presence of
drive(Ty)andskip(Ty): behave the same as their counterparts in theVisitandVisitMutderives described above.override(Ty): generatesenter_tyandexit_tymethods that do nothing, and avisit_tymethod that callsenter_ty, recurses withself.visit_inner()?, then callsexit_ty.override_skip(Ty): similar tooverride(Ty), but the default implementation does nothing, and noenter_Tyorexit_Tymethods are generated.
Note: the visitable_group interface makes it possible to write composable
visitor wrappers that provide reusable functionality. For an example, see
[derive_generic_visitor/tests/visitable_group_wrapper.rs].
So far we've seen visitors that visit a single value. This crate also supports "zipping" or "lockstep" visitors that visit two values of the same type at the same time, stopping early if they don't match. This can be useful to define custom comparison functions.
The structure matches what we've seen so far: DriveTwo represents a type that can be
lockstep-visited, and VisitTwo represents the corresponding visitors. Both can be derived,
and support the same option as their normal counterparts. There is no mutable version of this
visitor, under the assumption that it's not as useful.
Lockstep visitors are supported by the visitable_group macro by writing &two TraitName
where you would write &TraitName/&mut TraitName.