🌟

[Rustのデザインパターン学習] Visitor [振る舞い]

2023/01/15に公開

https://rust-unofficial.github.io/patterns/patterns/behavioural/visitor.html

はじめに

  • Foldパターンを投稿予定でしたが(GoFにはないパターンで十分理解できなかったため)Visitorパターンを学習することにしました。

    • ↓FoldとVisitorは似ているとのこと

      The fold pattern is similar to visitor but produces a new version of the visited data structure.

  • 前回学んだBuilderパターンを使用しています。

内容

  • Visitorパターンを使い、オブジェクト{Tags,BookInfo}に対してString, Vec<String>を得るメソッドを定義しています。
main.rs
#[macro_use]
extern crate derive_builder;
use std::vec;

#[derive(Debug,Builder,Clone)]
struct Tags {
    #[builder(setter(custom))]
    names:Vec<String>
}

impl TagsBuilder {
    fn names(mut self, names:Vec<&str>)->TagsBuilder{
        let mut new_vec:Vec<String> = Vec::new();
        for n in names  {
            new_vec.push(n.to_string());    
        }
        self.names = Some(new_vec);
        self
    }
}

#[derive(Debug,Builder)]
struct BookInfo {
    #[builder(setter(custom))]
    name:String,
    tags:Tags,
}

impl BookInfoBuilder {
    fn name(mut self, name:&str)->BookInfoBuilder{
        self.name = Some(name.to_string());
        self
    }
}

trait Visitor<T> {
    fn visit_title(title:&BookInfo)->T;
    fn visit_tags(tags:&Tags)->T;
}

impl Visitor<String> for BookInfo {
    fn visit_title(title:&BookInfo)-> String {
        let t = &title.name;
        t.to_string()
    }

    fn visit_tags(tags:&Tags)->String {
        let mut tag_names = String::from(""); 
        for n in tags.names.iter() {
            tag_names += n;
            tag_names += ", ";
        }
        tag_names[0..tag_names.len()-2].to_string()
    } 
}

impl Visitor<Vec<String>> for BookInfo {
    fn visit_title(title:&BookInfo)-> Vec<String> {
        let n = title.name.clone();
        let titles = vec![n];
        titles
    }

    fn visit_tags(tags:&Tags)->Vec<String> {
        tags.names.clone()
    } 
}

fn print_debug(tags:&Tags, title:&BookInfo){
    println!("{:#?}",tags);
    println!("{:#?}",title);
} 

fn print_visitor_example(tags:&Tags, bookinfo:&BookInfo){
    let title:String = BookInfo::visit_title(&bookinfo);
    let tag:String   = BookInfo::visit_tags(&bookinfo.tags);
    
    println!("{}",title);
    println!("{}\n",tag);

    let titles:Vec<String> = BookInfo::visit_title(&bookinfo);
    let tags:Vec<String>   = BookInfo::visit_tags(&bookinfo.tags);

    println!("{:?}",titles);
    println!("{:?}",tags);
}

fn main(){
    let tags = TagsBuilder::default()
        .names(vec!["rust","oreilly"])
        .build()
        .unwrap();
    
    let bak_tag = tags.clone();

    let oreilly_rust = BookInfoBuilder::default()
        .name("Programming Rust, 2nd Edition")
        .tags(tags)
        .build()
        .unwrap();
    
    print_struct(&bak_tag, &oreilly_rust);
    print_visitor_example(&bak_tag, &oreilly_rust)
}
Cargo.toml
[package]
name = "design_pattern"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
derive_builder = "0.12.0"
print_struct関数の実行結果
Tags {
    names: [
        "rust",
        "oreilly",
    ],
}
BookInfo {
    name: "Programming Rust, 2nd Edition",
    tags: Tags {
        names: [
            "rust",
            "oreilly",
        ],
    },
}

学習結果

  • 型注釈によってポリモーフィズムを実現し、値を取得できました。

    戻り値の型だけが違うので、ポリモーフィズムとは言えないかもしれません

fn print_visitor_example(tags:&Tags, bookinfo:&BookInfo){
    let title:String = BookInfo::visit_title(&bookinfo);
    let tag:String   = BookInfo::visit_tags(&bookinfo.tags);
    
    println!("{}",title);
    println!("{}\n",tag);

    let titles:Vec<String> = BookInfo::visit_title(&bookinfo);
    let tags:Vec<String>   = BookInfo::visit_tags(&bookinfo.tags);

    println!("{:?}",titles);
    println!("{:?}",tags);
}
print_visitor_example関数の実行結果
Programming Rust, 2nd Edition
rust, oreilly

["Programming Rust, 2nd Edition"]
["rust", "oreilly"]

その他

  • Visitor解説ページDiscussionのコード例について
    • 変数名で使用されている略語の意味がわからなかったので調べました。
      • lhsは左辺値 (left hand side)
      • rhsは右辺値 (right hand side)
  pub fn walk_expr(visitor: &mut Visitor, e: &Expr) {
    match *e {
        Expr::IntLit(_) => {},
        Expr::Add(ref lhs, ref rhs) => {
            visitor.visit_expr(lhs);
            visitor.visit_expr(rhs);
        }
        Expr::Sub(ref lhs, ref rhs) => {
            visitor.visit_expr(lhs);
            visitor.visit_expr(rhs);
        }
    }
}

Discussion