use std::{
cmp::min,
mem::{self, MaybeUninit},
ops::Range,
ptr,
};
pub struct ArrayChunks<T, const N: usize, const CHUNK_SZ: usize> {
array: [MaybeUninit<T>; N],
chunk_size: usize,
alive: Range<usize>,
}
pub struct ArrayChunk<T, const CHUNK_SZ: usize> {
array: [MaybeUninit<T>; CHUNK_SZ],
alive: Range<usize>,
}
impl<T, const N: usize, const CHUNK_SZ: usize> ArrayChunks<T, N, CHUNK_SZ> {
pub fn new(array: [T; N], chunk_size: usize) -> Self {
assert!(
CHUNK_SZ <= N,
"it does not make sense to choose a storage size of greater than N, \
since that is the minimum, statically known upper bound"
);
assert!(
CHUNK_SZ >= chunk_size,
"not enough storage for requested dynamic chunksize"
);
let copy = unsafe { mem::transmute_copy(&array) };
mem::forget(array);
Self {
alive: 0..N,
array: copy,
chunk_size,
}
}
}
impl<T, const CHUNK_SZ: usize> ArrayChunk<T, CHUNK_SZ> {
pub fn as_slice(&self) -> &[T] {
unsafe { &*(&self.array[self.alive.clone()] as *const [MaybeUninit<T>] as *const [T]) }
}
pub fn as_mut_slice(&mut self) -> &mut [T] {
unsafe { &mut *(&mut self.array[self.alive.clone()] as *mut [MaybeUninit<T>] as *mut [T]) }
}
}
impl<T, const N: usize, const CHUNK_SZ: usize> Drop for ArrayChunks<T, N, CHUNK_SZ> {
fn drop(&mut self) {
unsafe {
ptr::drop_in_place(&mut self.array[self.alive.clone()]);
}
}
}
impl<T, const CHUNK_SZ: usize> Drop for ArrayChunk<T, CHUNK_SZ> {
fn drop(&mut self) {
unsafe {
ptr::drop_in_place(self.as_mut_slice());
}
}
}
impl<T, const N: usize, const CHUNK_SZ: usize> Iterator for ArrayChunks<T, N, CHUNK_SZ> {
type Item = ArrayChunk<T, CHUNK_SZ>;
fn next(&mut self) -> Option<Self::Item> {
if self.alive.is_empty() {
None
} else {
let chunk_start = self.alive.start;
let chunk_end = min(chunk_start + self.chunk_size, self.alive.end);
self.alive.start = chunk_end;
let chunk_len = chunk_end - chunk_start;
let mut chunk = ArrayChunk {
array: MaybeUninit::uninit_array(),
alive: 0..chunk_len,
};
unsafe {
ptr::copy_nonoverlapping(
self.array.as_ptr().add(chunk_start),
chunk.array.as_mut_ptr(),
chunk_len,
);
}
Some(chunk)
}
}
}
impl<T, const CHUNK_SZ: usize> Iterator for ArrayChunk<T, CHUNK_SZ> {
type Item = T;
fn next(&mut self) -> Option<Self::Item> {
self.alive
.next()
.map(|ix| unsafe { self.array.get_unchecked(ix).assume_init_read() })
}
}
#[cfg(test)]
mod tests {
extern crate test;
use super::ArrayChunks;
use test::Bencher;
fn make_array<const N: usize>() -> [usize; N] {
core::array::from_fn(|i| i + 1)
}
#[bench]
fn basic_test(b: &mut Bencher) {
const N: usize = 32;
b.iter(|| {
let v = make_array::<N>();
let v2 = v.clone();
for (ix, c) in ArrayChunks::<_, N, 4>::new(v, 4).enumerate() {
let c = c.as_slice();
let expected = &v2[ix * 4..(ix + 1) * 4];
assert_eq!(expected, c);
}
});
}
#[bench]
fn dyamic_chunksz(b: &mut Bencher) {
const N: usize = 32;
b.iter(|| {
let v = make_array::<N>();
let v2 = v.clone();
for (ix, c) in ArrayChunks::<_, N, N>::new(v, 4).enumerate() {
let c = c.as_slice();
let expected = &v2[ix * 4..(ix + 1) * 4];
assert_eq!(expected, c);
}
});
}
#[test]
fn repeated_as_slice() {
let v: [i32; 10] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
for (ix, mut c) in ArrayChunks::<_, 10, 4>::new(v, 4).enumerate() {
match ix {
0 => {
assert_eq!(&[1, 2, 3, 4], c.as_slice());
assert_eq!(Some(1), c.next());
assert_eq!(&[2, 3, 4], c.as_slice());
assert_eq!(Some(2), c.next());
assert_eq!(&[3, 4], c.as_slice());
assert_eq!(Some(3), c.next());
assert_eq!(&[4], c.as_slice());
assert_eq!(Some(4), c.next());
assert_eq!(&[] as &[i32], c.as_slice());
assert_eq!(None, c.next());
}
1 => {
assert_eq!(&[5, 6, 7, 8], c.as_slice());
assert_eq!(Some(5), c.next());
assert_eq!(&[6, 7, 8], c.as_slice());
assert_eq!(Some(6), c.next());
assert_eq!(&[7, 8], c.as_slice());
assert_eq!(Some(7), c.next());
assert_eq!(&[8], c.as_slice());
assert_eq!(Some(8), c.next());
assert_eq!(&[] as &[i32], c.as_slice());
assert_eq!(None, c.next());
}
2 => {
assert_eq!(&[9, 10], c.as_slice());
assert_eq!(Some(9), c.next());
assert_eq!(&[10], c.as_slice());
assert_eq!(Some(10), c.next());
assert_eq!(&[] as &[i32], c.as_slice());
assert_eq!(None, c.next());
}
_ => panic!("expected no more chunks"),
}
}
}
#[test]
fn drop_iter_but_not_chunk() {
let v = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14];
let chunk = {
let mut chunks = ArrayChunks::<_, 14, 4>::new(v, 4);
chunks.next().unwrap()
};
assert_eq!(&[1, 2, 3, 4], chunk.as_slice());
}
}