Here's what I ended up doing:
// src/intersperse_sparse.rs use core::iter::Peekable; /// An iterator adaptor to insert a particular value /// every n elements of the adapted iterator. /// /// Iterator element type is `I::Item` pub struct IntersperseSparse<I> where I: Iterator, I::Item: Clone, { iter: Peekable<I>, step_length: usize, index: usize, separator: I::Item, } impl<I> IntersperseSparse<I> where I: Iterator, I::Item: Clone, { #[allow(unused)] // Although this function isn't explicitly exported, it is called in the default implementation of the IntersperseSparseAdapter, which is exported. fn new(iter: I, step_length: usize, separator: I::Item) -> Self { if step_length == 0 { panic!("Chunk size cannot be 0!") } Self { iter: iter.peekable(), step_length, separator, index: 0, } } } impl<I> Iterator for IntersperseSparse<I> where I: Iterator, I::Item: Clone, { type Item = I::Item; fn next(&mut self) -> Option<Self::Item> { if self.index == self.step_length && self.iter.peek().is_some() { self.index = 0; Some(self.separator.clone()) } else { self.index += 1; self.iter.next() } } } /// An iterator adaptor to insert a particular value created by a function /// every n elements of the adapted iterator. /// /// Iterator element type is `I::Item` pub struct IntersperseSparseWith<I, G> where I: Iterator, G: FnMut() -> I::Item, { iter: Peekable<I>, step_length: usize, index: usize, separator_closure: G, } impl<I, G> IntersperseSparseWith<I, G> where I: Iterator, G: FnMut() -> I::Item, { #[allow(unused)] // Although this function isn't explicitly exported, it is called in the default implementation of the IntersperseSparseAdapter, which is exported. fn new(iter: I, step_length: usize, separator_closure: G) -> Self { if step_length == 0 { panic!("Chunk size cannot be 0!") } Self { iter: iter.peekable(), step_length, separator_closure, index: 0, } } } impl<I, G> Iterator for IntersperseSparseWith<I, G> where I: Iterator, G: FnMut() -> I::Item, { type Item = I::Item; fn next(&mut self) -> Option<Self::Item> { if self.index == self.step_length && self.iter.peek().is_some() { self.index = 0; Some((self.separator_closure)()) } else { self.index += 1; self.iter.next() } } } /// Import this trait to use the `iter.intersperse_sparse(n, item)` and `iter.intersperse_sparse(n, ||item)` on all iterators. pub trait IntersperseSparseAdapter: Iterator { fn intersperse_sparse(self, chunk_size: usize, separator: Self::Item) -> IntersperseSparse<Self> where Self: Sized, Self::Item: Clone, { IntersperseSparse::new(self, chunk_size, separator) } fn intersperse_sparse_with<G>( self, chunk_size: usize, separator_closure: G, ) -> IntersperseSparseWith<Self, G> where Self: Sized, G: FnMut() -> Self::Item, { IntersperseSparseWith::new(self, chunk_size, separator_closure) } } impl<I> IntersperseSparseAdapter for I where I: Iterator {}
To use it:
// src/main.rs mod intersperse_sparse; use intersperse_sparse::IntersperseSparseAdapter; fn main() { let string = "abcdefg"; let new_string: String = string.chars().intersperse_sparse(3, '\n').collect(); assert_eq!(new_string, "abc\ndef\ng"); }