Advanced Projects
Custom Iterator Library
A custom iterator adapter library extends std::iter::Iterator through a blanket extension trait (impl<I: Iterator> IterExt for I), providing lazy, zero-allocation state machines that compose seamlessly with standard adapters (map, filter, collect) and optionally implement DoubleEndedIterator, ExactSizeIterator, and FusedIterator.
Lazy Adapter Composition Pipeline
Source Iterator: [1, 1, 2, 2, 2, 3, 1, 1]
|
v .run_length_encode() -> yields (1, 2), (2, 3), (3, 1), (1, 2)
|
v .interleave(other) -> alternates items from two iterators
|
v .chunk_exact_array::<2>() -> yields fixed-size [T; N] arrays on the stack
| Custom Adapter | State Stored in Struct | Yielded Item |
|---|---|---|
RunLengthEncode<I> |
Peekable<I> |
(I::Item, usize) consecutive run pairs |
Interleave<I, J> |
I, J, toggle: bool |
I::Item alternating between I and J |
ArrayChunks<I, const N: usize> |
I |
[I::Item; N] stack-allocated arrays without Vec heap allocation |
Complete Implementation
use std::iter::{FusedIterator, Peekable};
use std::mem::MaybeUninit;
pub struct RunLengthEncode<I: Iterator> {
iter: Peekable<I>,
}
impl<I> Iterator for RunLengthEncode<I>
where
I: Iterator,
I::Item: PartialEq,
{
type Item = (I::Item, usize);
fn next(&mut self) -> Option<Self::Item> {
let first = self.iter.next()?;
let mut count = 1usize;
while self.iter.peek() == Some(&first) {
self.iter.next();
count += 1;
}
Some((first, count))
}
}
pub struct Interleave<I, J> {
a: I,
b: J,
take_a: bool,
}
impl<I, J> Iterator for Interleave<I, J>
where
I: Iterator,
J: Iterator<Item = I::Item>,
{
type Item = I::Item;
fn next(&mut self) -> Option<Self::Item> {
if self.take_a {
self.take_a = false;
self.a.next().or_else(|| self.b.next())
} else {
self.take_a = true;
self.b.next().or_else(|| self.a.next())
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
let (a_lo, a_hi) = self.a.size_hint();
let (b_lo, b_hi) = self.b.size_hint();
let lo = a_lo.saturating_add(b_lo);
let hi = match (a_hi, b_hi) {
(Some(x), Some(y)) => x.checked_add(y),
_ => None,
};
(lo, hi)
}
}
pub struct ArrayChunks<I, const N: usize> {
iter: I,
}
impl<I: Iterator, const N: usize> Iterator for ArrayChunks<I, N> {
type Item = [I::Item; N];
fn next(&mut self) -> Option<Self::Item> {
let mut buf: [MaybeUninit<I::Item>; N] =
// SAFETY: An array of `MaybeUninit<T>` requires no initialization.
unsafe { MaybeUninit::uninit().assume_init() };
for i in 0..N {
match self.iter.next() {
Some(elem) => {
buf[i].write(elem);
}
None => {
// Drop the `0..i` elements already written before returning `None`
for initialized in &mut buf[0..i] {
unsafe {
initialized.assume_init_drop();
}
}
return None;
}
}
}
// SAFETY: All `N` elements were initialized in the loop above.
Some(unsafe { (&buf as *const _ as *const [I::Item; N]).read() })
}
fn size_hint(&self) -> (usize, Option<usize>) {
let (lo, hi) = self.iter.size_hint();
(lo / N, hi.map(|h| h / N))
}
}
impl<I: ExactSizeIterator, const N: usize> ExactSizeIterator for ArrayChunks<I, N> {}
impl<I: FusedIterator, const N: usize> FusedIterator for ArrayChunks<I, N> {}
pub trait IterExt: Iterator + Sized {
fn run_length_encode(self) -> RunLengthEncode<Self>
where
Self::Item: PartialEq,
{
RunLengthEncode {
iter: self.peekable(),
}
}
fn interleave<J>(self, other: J) -> Interleave<Self, J::IntoIter>
where
J: IntoIterator<Item = Self::Item>,
{
Interleave {
a: self,
b: other.into_iter(),
take_a: true,
}
}
fn array_chunks_const<const N: usize>(self) -> ArrayChunks<Self, N> {
assert!(N > 0, "chunk size N must be non-zero");
ArrayChunks { iter: self }
}
}
impl<I: Iterator> IterExt for I {}
fn main() {
let signal = ['A', 'A', 'A', 'B', 'B', 'C', 'A', 'A'];
let rle: Vec<(char, usize)> = signal.into_iter().run_length_encode().collect();
println!("Run-length encoded: {:?}", rle);
let evens = [2, 4, 6];
let odds = [1, 3, 5, 7, 9];
let interleaved: Vec<i32> = odds.into_iter().interleave(evens).collect();
println!("Interleaved stream: {:?}", interleaved);
let pairs: Vec<[i32; 2]> = interleaved.into_iter().array_chunks_const::<2>().collect();
println!("Stack-allocated 2-element chunks (trailing odd item dropped): {:?}", pairs);
}
Run-length encoded: [('A', 3), ('B', 2), ('C', 1), ('A', 2)]
Interleaved stream: [1, 2, 3, 4, 5, 6, 7, 9]
Stack-allocated 2-element chunks (trailing odd item dropped): [[1, 2], [3, 4], [5, 6], [7, 9]]