If the goal is to just get chunking behavior, then there are existing methods e.g. in the itertools crate, with further optimizations, e.g. the ability to avoid the need to allocate and create Vec
s entirely, if the items are consumed in the right order.
If the goal is to learn how to write iterators yourself, there are two basic approaches.
One approach is to use existing general iterator adapters / constructors, and implement the necessary logic in a closure, appropriately. One of the most general existing ways of constructing iterators in Rust is via the std::iter::from_fn function.
E.g.
fn chunked<I>(a: impl IntoIterator<Item = I>, chunk_size: usize) -> impl Iterator<Item = Vec<I>> {
let mut a = a.into_iter();
std::iter::from_fn(move || {
Some(a.by_ref().take(chunk_size).collect()).filter(|chunk: &Vec<_>| !chunk.is_empty())
})
}
fn main() {
for chunk in chunked([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 4) {
println!("{chunk:?}");
}
}
The (mostly equivalent) alternative is then to write your own iterator adapter type manually. The benefits would be that you have the ability to
- be able to add further optimizations, e.g. implement the
size_hint
- have a named type for the iterator
The downside is more boilerplate. You manually need to list the stuff the closure above captured automatically. But it’s not too bad either; a minimal translation would be
struct Chunked<I> {
iterator: I,
chunk_size: usize,
}
fn chunked<Collection>(a: Collection, chunk_size: usize) -> Chunked<Collection::IntoIter>
where
Collection: IntoIterator,
{
let iterator = a.into_iter();
Chunked {
iterator,
chunk_size,
}
}
impl<I: Iterator> Iterator for Chunked<I> {
type Item = Vec<I::Item>;
fn next(&mut self) -> Option<Self::Item> {
Some(self.iterator.by_ref().take(self.chunk_size).collect())
.filter(|chunk: &Vec<_>| !chunk.is_empty())
}
}
fn main() {
for chunk in chunked([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 4) {
println!("{chunk:?}");
}
}