#![no_std]
mod panic_guard;
mod remainder;
use core::{
mem::{forget, MaybeUninit},
ptr,
};
use panic_guard::PanicGuard;
use remainder::Remainder;
pub struct ConstChunks<const N: usize, I: Iterator> {
inner: I,
remainder: Option<Remainder<N, I::Item>>,
}
impl<const N: usize, I: Iterator> ConstChunks<N, I> {
const N_GT_ZERO: () = assert!(N > 0, "chunk size must be non-zero");
pub fn into_remainder(self) -> Option<Remainder<N, I::Item>> {
self.remainder
}
}
impl<const N: usize, I: Iterator> Iterator for ConstChunks<N, I> {
type Item = [I::Item; N];
fn next(&mut self) -> Option<Self::Item> {
let Some(first_item) = self.inner.next() else {
return None;
};
let mut array: [MaybeUninit<I::Item>; N] = unsafe { MaybeUninit::uninit().assume_init() };
let mut guard = PanicGuard {
slice: &mut array,
initialized: 0,
};
unsafe { guard.init_next_unchecked(first_item) };
for i in 1..N {
let Some(item) = self.inner.next() else {
forget(guard);
self.remainder = Some(Remainder {
remainder_chunk: array,
init_range: 0..i,
});
return None;
};
unsafe { guard.init_next_unchecked(item) };
}
forget(guard);
let init_arr = unsafe { ptr::addr_of!(array).cast::<[I::Item; N]>().read() };
Some(init_arr)
}
fn size_hint(&self) -> (usize, Option<usize>) {
let (lower, upper) = self.inner.size_hint();
(lower / N, upper.map(|upper| upper / N))
}
}
impl<const N: usize, I: ExactSizeIterator> ExactSizeIterator for ConstChunks<N, I> {
fn len(&self) -> usize {
self.inner.len() / N
}
}
pub trait IteratorConstChunks {
type Inner: Iterator;
#[cfg_attr(miri, doc = "```should_panic")]
#[cfg_attr(not(miri), doc = "```compile_fail,E0080")]
fn const_chunks<const N: usize>(self) -> ConstChunks<N, Self::Inner>;
}
impl<I: Iterator> IteratorConstChunks for I {
type Inner = Self;
fn const_chunks<const N: usize>(self) -> ConstChunks<N, Self::Inner> {
#[allow(clippy::let_unit_value)]
let _ = ConstChunks::<N, Self::Inner>::N_GT_ZERO;
ConstChunks {
inner: self,
remainder: None,
}
}
}
unsafe fn drop_slice<T>(slice: &mut [MaybeUninit<T>]) {
for init in slice {
init.assume_init_drop();
}
}
#[cfg(test)]
mod tests {
extern crate std;
use std::{
panic::catch_unwind,
string::{String, ToString},
vec,
vec::Vec,
};
use crate::IteratorConstChunks;
#[test]
fn test_panic_leak() {
struct PanicIter<I: Iterator> {
inner: I,
}
impl<I: Iterator> Iterator for PanicIter<I> {
type Item = I::Item;
fn next(&mut self) -> Option<Self::Item> {
Some(self.inner.next().unwrap())
}
}
let panic_iter = PanicIter {
inner: [String::from("1")].into_iter(),
};
let _ = catch_unwind(|| panic_iter.const_chunks::<4>().collect::<Vec<_>>());
}
#[test]
fn test_exhausted() {
let mut v_iter = (1..=5).map(|n| n.to_string()).const_chunks::<2>();
assert_eq!(v_iter.next(), Some([1, 2].map(|n| n.to_string())));
assert_eq!(v_iter.next(), Some([3, 4].map(|n| n.to_string())));
assert_eq!(v_iter.next(), None);
}
#[test]
fn test_remainder() {
let v = vec![1, 2, 3, 4, 5, 6];
let mut v_iter = v.into_iter().const_chunks::<4>();
let chunks = (&mut v_iter).collect::<Vec<_>>();
let remainder = v_iter.into_remainder().unwrap().collect::<Vec<_>>();
assert_eq!(chunks, vec![[1, 2, 3, 4]]);
assert_eq!(remainder, vec![5, 6]);
}
#[test]
fn test_remainder_leak() {
let mut v_iter = (1..=6).map(|n| n.to_string()).const_chunks::<4>();
let _ = (&mut v_iter).collect::<Vec<_>>();
assert_eq!(v_iter.next(), None);
let mut remainder = v_iter.into_remainder().unwrap();
assert_eq!(remainder.next(), Some(5.to_string()));
drop(remainder);
}
}