Codementor Events

Design Patterns in Rust: Visitor

Published Apr 03, 2023

The visitorpattern is a design pattern that allows for adding new operations to a collection of objects, without, and that is important, modifying the objects themselves.

If you want to implement the Visitor pattern in Rust, you can do that using traits.

First we will look at the basic structure:

VisitorPattern.drawio.png

We see two main functionalities:

  1. The visit functionality as implemented in the Visitor interface. This ensures that _Element_s can be visited.
  2. The accept functionality as implemented in the Element interface. This ensures that when an Element is visited, an operation can be performed.

Open your terminal in an empty directory and type:

cargo new rust_visitor
cd rust_visitor

We first define the Visitor trait:

pub trait Visitor {
    fn visit_person(&self,person:&Person);
    fn visit_organization(&self,o:&Organization);
}

And then the Element trait:

pub trait Element {
    fn accept(&self,visitor: &mut dyn Visitor);
}

In our example we can visit _Person_s and _Organization_s, we will start with the Person:

impl Element for Person {
    fn accept(&self,visitor: &mut dyn Visitor) {
        visitor.visit_person(self);
    }
}

impl Person {
    fn new(name:&str,email:&str)->Self {
        Self {
            email: email.to_string(),
            name: name.to_string()
        }
    }
}

A short explanation:

  1. First of all, we define the Element trait for the Person struct. As is clear all it does is calling the visit_person method add pass the Person object as the parameter..
  2. Next there is the implementation of the constructor. I use &str to prevent lifetime issues. Note that Self refers to the implementing type.

Now look at the implementation of the Organization struct:

pub struct Organization { 
    name: String,
    address: String
}

impl Organization {
    fn new(name: &str,address:&str)->Self {
        Self {
            name: name.to_string(),
            address: address.to_string()
        }
    }
}

This analogous to the Person struct as you can see.

The visitor is the client of all of these. We will implement a very simple one:

struct EmailVisitor;

impl Visitor for EmailVisitor {
    fn visit_person(&self,p:&Person) {
        println!("Sending email to {} at {}",p.name,p.email);
    }

    fn visit_organization(&self,o:&Organization) {
        println!("Sending mail to {} at {}",o.name,o.address);
    }
}

The EmailVisitor is defined as an empty struct in this simplified example.

Furthermore you can see how the visit_person and visit_organization methods are implemented.

Time to put it to the test:

fn main() {
    let mut elements:Vec<Box<dyn Element>>=Vec::new();
    
    let alice=Box::new(Person::new("Alice", "alices@example.com"));
    let bob=Box::new(Person::new("Bob", "bob@example.com"));
    let acme=Box::new(Organization::new("Acme Inc.","123 Main Str."));

    elements.push(alice);
    elements.push(acme);
    elements.push(bob);


    let mut email_visitor=EmailVisitor;
    for element in elements {
        element.accept(&mut email_visitor);
    }
    


}

A short breakdown will make things clearer:

  1. First we define an elements Vector which is of type Vec<Box<dyn Element>>. Why? dyn Element because Element is a trait, and therefore needs dynamic dispatching and Box because we do not know the size of the elements to be stored. After all, we only know they implement the Element trait.
  2. Next we instantiate three structs, one Organization and two _Person_s and we push them onto the Vector,
  3. We instantiate the EmailVisitor.
  4. Now iterate over the elements, and visit them, by calling the accept method.

As you can see, there is an elegant way of implementing the Visitor pattern in Rust. The compiler as always is helpful. What I needed to get used to was the use of Box.

Maybe there are more elegant ways to implement this, but since I am beginner in Rust I have not found them yet, so if you have any suggestions, let me know in the comments.

Discover and read more posts from Iede Snoek
get started