Chain of Responsibility with Iterator Chains in Rust

Explore the Chain of Responsibility design pattern in Rust using iterator chains for efficient request handling.

8.4. Chain of Responsibility with Iterator Chains

The Chain of Responsibility is a behavioral design pattern that allows an object to send a command without knowing which object will handle it. This pattern is particularly useful in scenarios where multiple objects can handle a request, but the handler is determined at runtime. In Rust, we can leverage iterator chains to implement this pattern efficiently, providing a flexible and dynamic way to process requests.

Understanding the Chain of Responsibility Pattern

Intent: The Chain of Responsibility pattern decouples the sender of a request from its receiver by allowing multiple objects to handle the request. The request is passed along a chain of potential handlers until one of them handles it.

Key Participants:

  • Handler: Defines an interface for handling requests.
  • ConcreteHandler: Handles requests it is responsible for and forwards requests to the next handler.
  • Client: Initiates the request to a handler in the chain.

Modeling Chain of Responsibility with Iterator Chains

In Rust, iterator chains provide a powerful mechanism to model the Chain of Responsibility pattern. By chaining iterators, we can create a sequence of handlers that process a request until it is handled. This approach is both efficient and idiomatic in Rust, leveraging the language’s strengths in functional programming and concurrency.

Using Closures and Functions as Handlers

Let’s start by implementing a simple Chain of Responsibility using closures as handlers. Consider a scenario where we have a series of handlers that process a string request:

 1fn main() {
 2    let handlers: Vec<Box<dyn Fn(&str) -> Option<String>>> = vec![
 3        Box::new(|req| {
 4            if req.contains("error") {
 5                Some("Handled by Error Handler".to_string())
 6            } else {
 7                None
 8            }
 9        }),
10        Box::new(|req| {
11            if req.contains("log") {
12                Some("Handled by Log Handler".to_string())
13            } else {
14                None
15            }
16        }),
17    ];
18
19    let request = "This is a log message";
20    for handler in handlers.iter() {
21        if let Some(response) = handler(request) {
22            println!("{}", response);
23            break;
24        }
25    }
26}

In this example, we define a vector of closures, each acting as a handler. The request is passed through each handler until one of them returns a response.

Dynamic Addition and Removal of Handlers

One of the strengths of using iterator chains is the ability to dynamically add or remove handlers. This flexibility is crucial in scenarios like middleware stacks in web servers or event processing systems.

 1fn main() {
 2    let mut handlers: Vec<Box<dyn Fn(&str) -> Option<String>>> = vec![];
 3
 4    // Add handlers dynamically
 5    handlers.push(Box::new(|req| {
 6        if req.contains("auth") {
 7            Some("Handled by Auth Handler".to_string())
 8        } else {
 9            None
10        }
11    }));
12
13    handlers.push(Box::new(|req| {
14        if req.contains("data") {
15            Some("Handled by Data Handler".to_string())
16        } else {
17            None
18        }
19    }));
20
21    let request = "This is a data request";
22    for handler in handlers.iter() {
23        if let Some(response) = handler(request) {
24            println!("{}", response);
25            break;
26        }
27    }
28}

In this example, we dynamically add handlers to the chain. This approach allows us to modify the chain at runtime, making it highly adaptable to changing requirements.

Use Cases for Chain of Responsibility with Iterator Chains

Event Processing

In event-driven systems, events are processed by a series of handlers. The Chain of Responsibility pattern is ideal for such scenarios, where each handler can decide whether to process the event or pass it to the next handler.

 1fn main() {
 2    let event_handlers: Vec<Box<dyn Fn(&str) -> Option<String>>> = vec![
 3        Box::new(|event| {
 4            if event == "click" {
 5                Some("Click event processed".to_string())
 6            } else {
 7                None
 8            }
 9        }),
10        Box::new(|event| {
11            if event == "hover" {
12                Some("Hover event processed".to_string())
13            } else {
14                None
15            }
16        }),
17    ];
18
19    let event = "click";
20    for handler in event_handlers.iter() {
21        if let Some(response) = handler(event) {
22            println!("{}", response);
23            break;
24        }
25    }
26}

Middleware in Web Servers

Middleware components in web servers often form a chain of responsibility. Each middleware can process a request, modify it, or pass it to the next middleware.

 1fn main() {
 2    let middleware_chain: Vec<Box<dyn Fn(&str) -> Option<String>>> = vec![
 3        Box::new(|req| {
 4            if req.starts_with("GET") {
 5                Some("GET request processed".to_string())
 6            } else {
 7                None
 8            }
 9        }),
10        Box::new(|req| {
11            if req.starts_with("POST") {
12                Some("POST request processed".to_string())
13            } else {
14                None
15            }
16        }),
17    ];
18
19    let request = "GET /index.html";
20    for middleware in middleware_chain.iter() {
21        if let Some(response) = middleware(request) {
22            println!("{}", response);
23            break;
24        }
25    }
26}

Design Considerations

When implementing the Chain of Responsibility pattern with iterator chains in Rust, consider the following:

  • Performance: Iterator chains are efficient, but ensure that the chain is not too long, as it may impact performance.
  • Error Handling: Decide how errors should be handled within the chain. Should they stop the chain, or should the request continue to the next handler?
  • State Management: If handlers need to maintain state, consider using structs with closures or functions.

Rust’s Unique Features

Rust’s ownership model and type system provide unique advantages when implementing the Chain of Responsibility pattern:

  • Safety: Rust’s borrow checker ensures that handlers do not cause data races or memory leaks.
  • Concurrency: Rust’s concurrency model allows handlers to be executed in parallel, improving performance in multi-threaded environments.

Differences and Similarities with Other Patterns

The Chain of Responsibility pattern is often compared to the Decorator pattern. While both involve a sequence of operations, the Chain of Responsibility pattern focuses on passing a request along a chain, whereas the Decorator pattern adds behavior to an object.

Visualizing the Chain of Responsibility

To better understand the flow of requests through a chain of handlers, let’s visualize the process using a sequence diagram:

    sequenceDiagram
	    participant Client
	    participant Handler1
	    participant Handler2
	    participant Handler3
	
	    Client->>Handler1: Send Request
	    Handler1-->>Client: Handle or Pass
	    Handler1->>Handler2: Pass Request
	    Handler2-->>Client: Handle or Pass
	    Handler2->>Handler3: Pass Request
	    Handler3-->>Client: Handle or Pass

In this diagram, the client sends a request to the first handler. Each handler decides whether to handle the request or pass it to the next handler.

Try It Yourself

Experiment with the code examples provided. Try adding new handlers, modifying existing ones, or changing the conditions under which handlers process requests. This hands-on approach will deepen your understanding of the Chain of Responsibility pattern in Rust.

Knowledge Check

  • What is the primary purpose of the Chain of Responsibility pattern?
  • How can iterator chains be used to implement this pattern in Rust?
  • What are some common use cases for this pattern?
  • How does Rust’s ownership model benefit the implementation of this pattern?

Embrace the Journey

Remember, mastering design patterns is a journey. As you explore the Chain of Responsibility pattern in Rust, you’ll gain insights into how to build flexible and efficient systems. Keep experimenting, stay curious, and enjoy the process!

Quiz Time!

Loading quiz…
Revised on Thursday, April 23, 2026