Fold Step

The fold step accumulates a result by processing each element in a traversal, operating like the standard Rust fold operation but specifically for graph traversals. It's a powerful terminal operation that builds a single value from all elements in the stream.

Fold step diagram showing accumulation of values from traversal elements

In this diagram:

  • An Input Stream contains elements A (age=30), B (age=25), and C (age=40).
  • The .fold(0, |acc, v| acc + v.age()) step is applied. It starts with an initial accumulator value of 0.
  • The Accumulator visualization shows the value being updated as each element is processed:
    • Initial: 0
    • After A: 0 + 30 = 30
    • After B: 30 + 25 = 55
    • After C: 55 + 40 = 95
  • The Final Result box shows the final accumulated value (95).
  • This step Terminates Walker, meaning no further Graph API steps can be chained after fold.

Syntax

walker.fold(initial_value, |accumulator, element, context| {
    // accumulation logic
})

Parameters

  • initial_value: The starting value for the accumulator
  • f: A closure that takes:
    • The current accumulator value
    • A reference to the current element (vertex or edge)
    • The current element's context
    • Returns the updated accumulator value

Return Value

Returns the final accumulated value after processing all elements in the traversal.

Example

pub fn fold_example() {
    // Create a graph with standard test data
    let graph = standard_populated_graph();

    // Calculate the sum of ages of all people using fold
    let total_age = graph
        .walk()
        .vertices(VertexSearch::scan())
        .filter_person()
        .fold(0, |acc, vertex, _ctx| {
            // Add the person's age to the accumulator
            if let Some(person) = vertex.project::<Person<_>>() {
                acc + person.age() as u32
            } else {
                acc
            }
        });

    println!("Total age of all people: {}", total_age);

    // Example with context: Collect names of people older than the context age
    let initial_age_threshold = 30;
    let names_older_than_threshold = graph
        .walk()
        .vertices(VertexSearch::scan())
        .filter_person()
        .push_context(|_, _| initial_age_threshold) // Push the threshold as context
        .fold(Vec::new(), |mut names, vertex, ctx| {
            if let Some(person) = vertex.project::<Person<_>>() {
                // Use context (threshold) in the fold logic
                if person.age() as u32 > **ctx {
                    names.push(person.name().to_string());
                }
            }
            names
        });

    println!(
        "Names of people older than {}: {:?}",
        initial_age_threshold, names_older_than_threshold
    );
}

Best Practices

  • Choose an appropriate initial value that handles edge cases (empty traversals)
  • Design fold closures to be commutative when possible for predictable results
  • Use type annotations for complex accumulator types to improve readability
  • Consider specialized steps like count() when their behavior matches your needs

Common Use Cases

  • Aggregation: Calculating sums, averages, or other numerical aggregates
  • Collection building: Creating custom collections or data structures from traversal results
  • State tracking: Building a final state that incorporates data from all elements
  • Custom reductions: Implementing specialized reduction operations not covered by built-in steps