4

I found an interesting implementation of the visitor pattern in Rust:

pub trait Visitor<T> {
    fn visit(&mut self, t: &T);
}

pub trait Visitable: Sized {
    fn accept<T>(&self, t: &mut T)
    where
        T: Visitor<Self>,
    {
        t.visit(self);
    }
}

As Visitable is not object safe, I'm unable to store a Vec of Visitable objects. I found a solution to a similar problem, would it be possible to do a similar solution for Visitable?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
prinsen
  • 748
  • 6
  • 16
  • *Questions seeking debugging help must include [...] code necessary to reproduce it **in the question itself**.* — please summarize the content of your second link. – Shepmaster Dec 24 '18 at 14:16
  • I'm not sure if this is a very useful formulation of the pattern. In a typical OOP implementation, you'd use method overloading so that when a visitee calls `visit` on the visitor, it will call a specific overload. Doing this in Rust would require either an enum for visitees or else an impl of `Visitor` for each _permutation_ of visitor/visitee. And you'd still lose a lot of the benefits (unless we get some form of trait impl specialization in the language). – Peter Hall Dec 24 '18 at 15:51
  • I know it can be done with an enum of all Visitable types. The thing I liked with the GitHub impl is that it avoids it. However then Visitable isn't easily stored in a Vec – prinsen Dec 24 '18 at 15:58

1 Answers1

1

Visitable is a trait and traits have no size (implementors are not guaranteed to have the same size). You need to store something that has a size. Wrap the values in a Box and then use it like following:

trait VisitorTrait {
    fn visit(&self, visitable: &Box<VisitableTrait>);
}

struct Visitor1 {}
struct Visitor2 {}

impl VisitorTrait for Visitor1 {
    fn visit(&self, visitable: &Box<VisitableTrait>) {
        println!("visited via Visitor1");
        visitable.accept()
    }
}

impl VisitorTrait for Visitor2 {
    fn visit(&self, visitable: &Box<VisitableTrait>) {
        println!("visited via Visitor2");
        visitable.accept()
    }
}

trait VisitableTrait {
    fn accept(&self);
}

#[derive(Clone)]
struct Visitable1 {}
#[derive(Clone)]
struct Visitable2 {}

impl VisitableTrait for Visitable1 {
    fn accept(&self) {
        println!("accepted1.");
    }
}

impl VisitableTrait for Visitable2 {
    fn accept(&self) {
        println!("accepted2.");
    }
}

fn main() {
    let visitor1 = Visitor1 {};
    let visitor2 = Visitor2 {};
    let visitable1 = Visitable1 {};
    let visitable2 = Visitable2 {};

    let mut visitors: Vec<Box<VisitorTrait>> = Vec::new();
    let mut visitables: Vec<Box<VisitableTrait>> = Vec::new();

    visitors.push(Box::new(visitor1));
    visitors.push(Box::new(visitor2));

    visitables.push(Box::new(visitable1));
    visitables.push(Box::new(visitable2));

    for visitable in visitables.iter() {
        for visitor in visitors.iter() {
            visitor.visit(visitable.clone());
        }
    }
}

Playground

We declare two different traits for visitor and the visitables and traversing the visitables with the help of visitors with two for loops.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Akiner Alkan
  • 6,145
  • 3
  • 32
  • 68
  • This does not allow to pass in different visitors, or do I miss something? – prinsen Dec 24 '18 at 11:54
  • 1
    I think you got visitor pattern wrong, accept should take a visitor – prinsen Dec 24 '18 at 13:59
  • If you look at the visitor impl in the link, it allows a visitor impl to implement visitor for a set of types implementing Visitable, and when you call object.accept(visitor), the correct impl is called depending on the type of object – prinsen Dec 24 '18 at 14:06