Discussion about Visitor pattern #236
Replies: 6 comments 2 replies
-
Certainly. You just need to define two traits: trait Data {
fn accept<V: Visitor>(&self, visitor: &mut V) -> V::Result;
}
trait Visitor {
type Result;
fn visit_stmt(&mut self, stmt: &Stmt) -> Self::Result;
fn visit_name(&mut self, name: &Name) -> Self::Result;
fn visit_expr(&mut self, expr: &Expr) -> Self::Result;
}
impl Data for Stmt {
fn accept<V: Visitor>(&self, visitor: &mut V) -> V::Result {
visitor.visit_stmt(self)
}
}
impl Data for Name {
fn accept<V: Visitor>(&self, visitor: &mut V) -> V::Result {
visitor.visit_name(self)
}
}
impl Data for Expr {
fn accept<V: Visitor>(&self, visitor: &mut V) -> V::Result {
visitor.visit_expr(self)
}
}
impl Visitor for Interpreter {
fn visit_stmt(&mut self, stmt: &Stmt) -> Self::Result { unimplemented!() }
fn visit_name(&mut self, name: &Name) -> Self::Result { unimplemented!() }
fn visit_expr(&mut self, expr: &Expr) -> Self::Result { unimplemented!() }
} Serde uses visitors in this way to decouple serialization/deserialization from the data format. |
Beta Was this translation helpful? Give feedback.
-
Follow up question. With specialization you implement the visitor pattern similar to this:
What are the thoughts on doing something like this vs creating a |
Beta Was this translation helpful? Give feedback.
-
That's clever! In fact, if you don't need a default What's particular about that technique, as opposed to traditional visitors, is that an external crate could add a new Another interesting fact is that if you control the value to be visited and you provide your own visitor, then you only need to implement the A disadvantage of that technique is that it's harder to make a trait object for a
Then the bound |
Beta Was this translation helpful? Give feedback.
-
An issue with using generics here is that you can't invoke let vs: &[&dyn Visitor] = &[&V0, &V1];
let ds: &[&dyn Data] = &[&Stmt, &Name, &Expr];
for d in ds {
for v in vs {
d.accept(*v);
}
} One can easily add new visitors, but adding new data requires adding methods to the I'm not aware of a safe way to do dynamic multiple dispatch that is extensible in both arguments. This comment by Graydon Hoare (and the whole post above) offers some insight into the design decision: https://graydon2.dreamwidth.org/189377.html?thread=627649#cmt627649 |
Beta Was this translation helpful? Give feedback.
-
Following my comment on HN it would be great to specify use cases when the visitor pattern should be used. Most of the time, pattern matching should be good enough and is more idiomatic Rust. |
Beta Was this translation helpful? Give feedback.
-
Changed the title of the issue because visitor pattern exists here |
Beta Was this translation helpful? Give feedback.
-
Maybe there is something I'm missing because the lack of Rust syntax/concepts understanding (I'm still learning, sorry) but the goal of the Visitor pattern is not only to "allow multiple different algorithms to be written over the same data". The main reason when visitor pattern is used is to allow dynamic double dispatch. Say you have a pair of abstract Visitor and Data and you don't know at compile time what the actual type of Data and Visitor is, you can just say data.accept(visitor) and let the magic happen.
Just look at https://en.wikipedia.org/wiki/Visitor_pattern#C.2B.2B_example
This is perfectly doable in any OO language.
Refering to the example, how can I have a
let d : Data = some of(Stmt, Name, Expr, ...);
let v : Visitor = some of(Interpreter, ...);
and say d.accept(v) without doing match on types anywhere.
Can this be done in Rust?
Beta Was this translation helpful? Give feedback.
All reactions