Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Visitors

The visitor pattern traverses AST nodes without modifying them.

Visitor Trait

/// Visitor trait for traversing TOML AST nodes.
///
/// Implement the `visit_*` methods you care about. Default implementations
/// call the corresponding `walk_*` methods to traverse children.
pub trait TomlVisitor {
    fn visit_document(&mut self, doc: &Document) {
        self.walk_document(doc);
    }

    fn visit_document_item(&mut self, item: &DocumentItem) {
        self.walk_document_item(item);
    }

    fn visit_key_value(&mut self, kv: &KeyValue) {
        self.walk_key_value(kv);
    }

    fn visit_key(&mut self, key: &Key) {
        self.walk_key(key);
    }

    fn visit_simple_key(&mut self, key: &SimpleKey) {
        let _ = key; // leaf node
    }

    fn visit_value(&mut self, value: &Value) {
        self.walk_value(value);
    }

    fn visit_table(&mut self, table: &Table) {
        self.walk_table(table);
    }

    fn visit_array(&mut self, array: &Array) {
        self.walk_array(array);
    }

    fn visit_inline_table(&mut self, table: &InlineTable) {
        self.walk_inline_table(table);
    }

    // Walk methods traverse child nodes

    fn walk_document(&mut self, doc: &Document) {
        for item in &doc.items {
            self.visit_document_item(item);
        }
    }

    fn walk_document_item(&mut self, item: &DocumentItem) {
        match item {
            DocumentItem::Trivia(_) => {}
            DocumentItem::KeyValue(kv) => self.visit_key_value(&kv.value),
            DocumentItem::Table(table) => self.visit_table(&table.value),
        }
    }

    fn walk_key_value(&mut self, kv: &KeyValue) {
        self.visit_key(&kv.key.value);
        self.visit_value(&kv.value.value);
    }

    fn walk_key(&mut self, key: &Key) {
        match key {
            Key::Bare(tok) => self.visit_simple_key(&SimpleKey::Bare(tok.clone())),
            Key::Quoted(tok) => self.visit_simple_key(&SimpleKey::Quoted(tok.clone())),
            Key::Dotted(dotted) => {
                self.visit_simple_key(&dotted.first.value);
                for (_, k) in &dotted.rest {
                    self.visit_simple_key(&k.value);
                }
            }
        }
    }

    fn walk_value(&mut self, value: &Value) {
        match value {
            Value::Array(arr) => self.visit_array(arr),
            Value::InlineTable(tbl) => self.visit_inline_table(tbl),
            _ => {}
        }
    }

    fn walk_table(&mut self, table: &Table) {
        self.visit_key(&table.name.value);
        for item in &table.items {
            match item {
                TableItem::Trivia(_) => {}
                TableItem::KeyValue(kv) => self.visit_key_value(&kv.value),
            }
        }
    }

    fn walk_array(&mut self, array: &Array) {
        for item in &array.items {
            self.visit_value(&item.value.value);
        }
    }

    fn walk_inline_table(&mut self, table: &InlineTable) {
        for item in &table.items {
            self.visit_key_value(&item.kv.value);
        }
    }
}

Two method types:

  • visit_*: Override to handle specific nodes, calls walk_* by default
  • walk_*: Traverses children, typically not overridden

Example: Collecting Keys

/// Example visitor: collects all keys in the document.
pub struct KeyCollector {
    pub keys: Vec<String>,
}

impl KeyCollector {
    pub fn new() -> Self {
        Self { keys: Vec::new() }
    }

    pub fn collect(doc: &Document) -> Vec<String> {
        let mut collector = Self::new();
        collector.visit_document(doc);
        collector.keys
    }
}

impl Default for KeyCollector {
    fn default() -> Self {
        Self::new()
    }
}

impl TomlVisitor for KeyCollector {
    fn visit_simple_key(&mut self, key: &SimpleKey) {
        let name = match key {
            SimpleKey::Bare(tok) => tok.0.clone(),
            SimpleKey::Quoted(tok) => tok.0.clone(),
        };
        self.keys.push(name);
    }
}

Usage:

let mut collector = KeyCollector::new();
collector.visit_document(&doc.value);
// collector.keys now contains all key names

Example: Counting Values

/// Example visitor: counts values by type.
#[derive(Default, Debug)]
pub struct ValueCounter {
    pub strings: usize,
    pub integers: usize,
    pub booleans: usize,
    pub arrays: usize,
    pub inline_tables: usize,
}

impl ValueCounter {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn count(doc: &Document) -> Self {
        let mut counter = Self::new();
        counter.visit_document(doc);
        counter
    }
}

impl TomlVisitor for ValueCounter {
    fn visit_value(&mut self, value: &Value) {
        match value {
            Value::String(_) => self.strings += 1,
            Value::Integer(_) => self.integers += 1,
            Value::True(_) | Value::False(_) => self.booleans += 1,
            Value::Array(arr) => {
                self.arrays += 1;
                self.visit_array(arr);
            }
            Value::InlineTable(tbl) => {
                self.inline_tables += 1;
                self.visit_inline_table(tbl);
            }
        }
    }
}

Example: Finding Tables

/// Example visitor: finds all table names.
pub struct TableFinder {
    pub tables: Vec<String>,
}

impl TableFinder {
    pub fn new() -> Self {
        Self { tables: Vec::new() }
    }

    pub fn find(doc: &Document) -> Vec<String> {
        let mut finder = Self::new();
        finder.visit_document(doc);
        finder.tables
    }

    fn key_to_string(key: &Key) -> String {
        match key {
            Key::Bare(tok) => tok.0.clone(),
            Key::Quoted(tok) => format!("\"{}\"", tok.0),
            Key::Dotted(dotted) => {
                let mut parts = vec![Self::simple_key_to_string(&dotted.first.value)];
                for (_, k) in &dotted.rest {
                    parts.push(Self::simple_key_to_string(&k.value));
                }
                parts.join(".")
            }
        }
    }

    fn simple_key_to_string(key: &SimpleKey) -> String {
        match key {
            SimpleKey::Bare(tok) => tok.0.clone(),
            SimpleKey::Quoted(tok) => format!("\"{}\"", tok.0),
        }
    }
}

impl Default for TableFinder {
    fn default() -> Self {
        Self::new()
    }
}

impl TomlVisitor for TableFinder {
    fn visit_table(&mut self, table: &Table) {
        self.tables.push(Self::key_to_string(&table.name.value));
        self.walk_table(table);
    }
}

Visitor vs Direct Traversal

Visitor pattern when:

  • Multiple traversal operations needed
  • Want to separate traversal from logic
  • Building analysis tools

Direct recursion when:

  • One-off transformation
  • Simple structure
  • Need mutation

Transforming Visitors

For mutation, use a mutable visitor or return new nodes:

pub trait TomlTransform {
    fn transform_value(&mut self, value: Value) -> Value {
        self.walk_value(value)
    }

    fn walk_value(&mut self, value: Value) -> Value {
        match value {
            Value::Array(arr) => Value::Array(self.transform_array(arr)),
            other => other,
        }
    }
    // ...
}

Visitor Tips

Selective Traversal

Override visit_* to stop descent:

fn visit_inline_table(&mut self, _table: &InlineTable) {
    // Don't call walk_inline_table - skip inline table contents
}

Accumulating Results

Use struct fields:

struct Stats {
    tables: usize,
    keys: usize,
    values: usize,
}

impl TomlVisitor for Stats {
    fn visit_table(&mut self, table: &Table) {
        self.tables += 1;
        self.walk_table(table);
    }
    // ...
}

Context Tracking

Track path during traversal:

struct PathTracker {
    path: Vec<String>,
    paths: Vec<String>,
}

impl TomlVisitor for PathTracker {
    fn visit_table(&mut self, table: &Table) {
        self.path.push(table_name(table));
        self.paths.push(self.path.join("."));
        self.walk_table(table);
        self.path.pop();
    }
}