How to Use the Service Pattern for Composable Systems

Use Rust's Iterator and AsyncIterator traits with adapters like filter and map to build composable, efficient data processing pipelines.

Use the Iterator trait and its adapters to chain operations, then collect the result into a concrete type. This pattern composes small, single-purpose functions into complex data pipelines without intermediate allocations.

let numbers = vec![1, 2, 3, 4, 5];
let result: Vec<i32> = numbers
    .into_iter()
    .filter(|&n| n % 2 == 0)
    .map(|n| n * 2)
    .collect();

assert_eq!(result, vec![4, 8, 12]);

For asynchronous systems, use the AsyncIterator trait with await to compose non-blocking operations.

use std::pin::Pin;
use std::task::{Context, Poll};

// Define an async iterator trait implementation
struct AsyncCounter {
    count: usize,
}

impl AsyncIterator for AsyncCounter {
    type Item = usize;

    fn poll_next(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
        let this = self.get_mut();
        if this.count < 5 {
            this.count += 1;
            Poll::Ready(Some(this.count))
        } else {
            Poll::Ready(None)
        }
    }
}

// Usage in an async context (requires a runtime like tokio)
// let mut counter = AsyncCounter { count: 0 };
// while let Some(n) = counter.next().await {
//     println!("{n}");
// }

The FromIterator trait allows you to define how your custom types are constructed from these iterators, enabling seamless integration with .collect().

use std::iter::FromIterator;

struct MyCollection(Vec<i32>);

impl FromIterator<i32> for MyCollection {
    fn from_iter<I: IntoIterator<Item = i32>>(iter: I) -> Self {
        MyCollection(iter.collect())
    }
}

let data = vec![1, 2, 3];
let collection: MyCollection = data.into_iter().collect();

This approach leverages Rust's zero-cost abstractions to build flexible, composable systems where logic is separated from data flow.