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

Boxed Step

The boxed step performs type erasure to reduce monomorphization and improve compile times. It wraps the current walker in a SmallBox, breaking the chain of nested generic types that can grow exponentially in complex traversals.

The boxed walker preserves all functionality including context access and modification, making it a drop-in replacement for any walker step.

Boxed step diagram showing type complexity reduction through type erasure

In this diagram:

  • An Input Stream contains a walker with complex nested types like Endpoints<Edges<Vertices<Empty>>>.
  • The .boxed() step performs type erasure, wrapping the walker in a SmallBox.
  • The Output Stream contains a simplified BoxedVertexWalker type that hides the complexity.
  • Type Complexity: Before boxing shows deeply nested generics; after boxing shows a simple erased type.

Syntax

walker.boxed()

Parameters

This step takes no parameters.

Return Value

Returns a new walker with erased types that behaves identically to the original but with simplified type signatures.

Examples

Basic Type Complexity Reduction

Reduce type complexity in multi-step traversals:

    // Without boxing - creates complex nested types like:
    // Endpoints<Edges<Endpoints<Edges<Vertices<Empty>>>>>
    let _complex_type = graph
        .walk()
        .vertices(Vertex::person()) // Start with Person vertices
        .edges_out() // Get outgoing edges
        .head() // Move to target vertices
        .edges_out() // Get their outgoing edges
        .head(); // Move to final target vertices
    // This creates deeply nested generic types that slow compilation

    // With strategic boxing - simplified types
    let people_connections: Vec<_> = graph
        .walk()
        .vertices(Vertex::person()) // Start with Person vertices
        .edges_out() // Get outgoing edges
        .boxed() // ← Breaks type complexity chain
        .head() // Move to target vertices
        .edges_out() // Get their outgoing edges
        .boxed() // ← Further reduces complexity
        .head() // Move to final target vertices
        .collect();

    println!("Found {} connected vertices through people", people_connections.len());

Strategic Boxing in Complex Traversals

Use boxing at logical checkpoints in long traversals:

    // Complex multi-hop traversal with strategic boxing
    let projects_via_people: Vec<_> = graph
        .walk()
        .vertices(Vertex::person()) // Start with Person vertices
        .filter_by_person(|person, _| person.age() > 25) // Filter by age
        .take(5) // Limit to first 5 people
        .boxed() // ← Box after initial filtering
        .edges_out() // Get their outgoing relationships
        .head() // Move to connected vertices
        .filter_project() // Keep only Project vertices
        .boxed() // ← Box after project filtering
        .collect();

    println!("Found {} projects created by people over 25", projects_via_people.len());

Storage in Collections

Enable storage of walkers in data structures:

    // Store different walker types in a collection using boxing
    let vertex_walkers: Vec<Box<dyn Iterator<Item = _>>> = vec![
        // First walker: all people
        Box::new(
            graph
                .walk()
                .vertices(Vertex::person()) // Type-safe Person lookup
                .boxed() // ← Boxing makes storage possible
                .into_iter()
        ),
        // Second walker: young people only
        Box::new(
            graph
                .walk()
                .vertices(Vertex::person())
                .filter_by_person(|person, _| person.age() < 30)
                .boxed() // ← Boxing makes storage possible
                .into_iter()
        ),
    ];

    println!("Created {} different walker configurations", vertex_walkers.len());

Builder Pattern Integration

Use boxing to enable flexible walker construction:

    // Builder pattern enabled by boxing
    struct PersonTraversalBuilder<'graph, G: Graph> {
        walker: Option<Box<dyn Iterator<Item = G::VertexId> + 'graph>>,
    }

    impl<'graph, G: Graph> PersonTraversalBuilder<'graph, G> {
        fn add_young_people(&mut self, graph: &'graph G) {
            let walker = graph
                .walk()
                .vertices(Vertex::person()) // Type-safe Person lookup
                .filter_by_person(|person, _| person.age() < 30) // Filter young people
                .boxed() // ← Boxing enables storage in the builder
                .into_iter();
            self.walker = Some(Box::new(walker));
        }
    }

    let mut builder = PersonTraversalBuilder { walker: None };
    builder.add_young_people(&graph);

Context Preservation

Boxing preserves context functionality - all context operations work normally:

    // Context functionality is fully preserved through boxing
    #[derive(Clone, Debug)]
    struct TraversalMetrics {
        hops: u32,
        people_visited: u32,
    }

    let metrics_result: Vec<_> = graph
        .walk()
        .push_context(TraversalMetrics { hops: 0, people_visited: 0 })
        .vertices(Vertex::person())
        .mutate_context(|_vertex, ctx| {
            ctx.people_visited += 1; // Track people visited
        })
        .edges_out()
        .boxed() // ← Boxing preserves context functionality
        .mutate_context(|_edge, ctx| {
            ctx.hops += 1; // Context operations work normally after boxing
        })
        .head()
        .map(|vertex, context| {
            // Context is accessible in later steps
            println!("Vertex {:?} reached after {} hops, visited {} people", 
                    vertex, context.hops, context.people_visited);
            vertex
        })
        .collect();

    println!("Context-aware traversal found {} vertices", metrics_result.len());

Best Practices

  • Use boxed() after 4+ chained operations to prevent type explosion
  • Place boxing at logical boundaries in complex traversals (after filtering, major operations)
  • Apply when compile times become slow due to deep walker nesting
  • Consider boxing when storing walkers in collections or data structures
  • Avoid boxing simple 2-3 step chains where type complexity is manageable
  • Measure the 5-15% runtime overhead against compilation benefits for your use case

Common Use Cases

  • Compile-time optimization: Reducing build times for applications with many complex walker chains
  • Type simplification: Making complex walker types manageable in library interfaces
  • Collection storage: Enabling storage of different walker types in Vec or other collections
  • Builder patterns: Simplifying type signatures in reusable walker construction utilities
  • Library development: Providing clean APIs without exposing deeply nested generic types