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.