1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
//! This crate provides an extension trait that lets you chunk iterators into constant-length arrays using `const` generics.
//!
//! See the [`IteratorConstChunks::const_chunks`] docs for more info.
//!
//! ```rust
//! use const_chunks::IteratorConstChunks;
//!
//! let v = vec![1, 2, 3, 4, 5, 6];
//! let mut v_iter = v.into_iter().const_chunks::<2>();
//! assert_eq!(v_iter.next(), Some([1,2]));
//! assert_eq!(v_iter.next(), Some([3,4]));
//! assert_eq!(v_iter.next(), Some([5,6]));
//! ```
use std::mem::MaybeUninit;
/// An iterator that iterates over constant-length chunks, where the length is known at compile time.
///
/// This struct is created by the [`IteratorConstChunks::const_chunks`]. See its documentation for more.
pub struct ConstChunks<const N: usize, I: Iterator> {
/// The inner iterator from which we take chunks.
inner: I,
}
impl<const N: usize, I: Iterator> Iterator for ConstChunks<N, I> {
type Item = [I::Item; N];
fn next(&mut self) -> Option<Self::Item> {
unsafe {
// Create array of unitialized values
let mut arr: [MaybeUninit<I::Item>; N] = MaybeUninit::uninit().assume_init();
// Initialize items
for i in 0..N {
// Get the next iterator item
let Some(val) = self.inner.next() else {
// If there wasn't enough items to fill the array then
// manually drop the initialized values
for v in &mut arr[..i] {
v.assume_init_drop();
}
// early-return None.
return None;
};
// Initialize item
arr[i].write(val);
}
// Cast to an array of definitely initialized `I::Item`s
// TODO: use `array_assume_init` when stabilized.
let init_arr = (&arr as *const _ as *const [I::Item; N]).read();
Some(init_arr)
}
}
}
/// An extension trait providing [`Iterator`]s with the capability to iterate
/// over chunks of items.
pub trait IteratorConstChunks {
/// The type of iterator from which we take chunks.
type Inner: Iterator;
/// This function returns an iterator over constant-length chunks of items, where
/// the length is provided as a const-generic. This function assumes that the number
/// of items can be divided into an integer number of chunks. If there are not
/// enough items to fully fill a chunk, then the items are consumed, but no chunk
/// will be yielded.
///
/// # Examples
///
/// Basic usage:
///
/// ```
/// use const_chunks::IteratorConstChunks;
///
/// let v = vec![1, 2, 3, 4, 5, 6];
/// let mut v_iter = v.into_iter().const_chunks::<2>();
/// assert_eq!(v_iter.next(), Some([1,2]));
/// assert_eq!(v_iter.next(), Some([3,4]));
/// assert_eq!(v_iter.next(), Some([5,6]));
/// ```
///
/// When the number of items in the iterator cannot be divided exactly
/// into chunks, then the iterator will be fully consumed, but the last
/// chunk will not be yielded.
/// ```
/// use const_chunks::IteratorConstChunks;
///
/// let v = (1..=5).map(|n| n.to_string()).collect::<Vec<String>>(); // Five items cannot fit into chunks of length 2!
/// let mut v_iter = v.into_iter().const_chunks::<2>();
/// assert_eq!(v_iter.next(), Some([String::from("1"),String::from("2")]));
/// assert_eq!(v_iter.next(), Some([String::from("3"),String::from("4")]));
/// assert_eq!(v_iter.next(), None); // `None`, even though there was still one item
/// ```
fn const_chunks<const N: usize>(self) -> ConstChunks<N, Self::Inner>;
}
/// Blanket implementation over all iterators.
impl<I: Iterator> IteratorConstChunks for I {
type Inner = Self;
fn const_chunks<const N: usize>(self) -> ConstChunks<N, Self::Inner> {
ConstChunks { inner: self }
}
}