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]]