about summary refs log tree commit diff
diff options
context:
space:
mode:
authorRoss MacArthur <ross@macarthur.io>2022-01-02 18:31:56 +0200
committerMaybe Waffle <waffle.lapkin@gmail.com>2022-08-01 16:39:27 +0400
commitca3d1010bb5f8ed6b7897c1d85f794857e00caa4 (patch)
tree9930df89cf68a8558905675d22aa790fb44b0663
parent1f5d8d49eb6111931091f700d07518cd2b80bc18 (diff)
downloadrust-ca3d1010bb5f8ed6b7897c1d85f794857e00caa4.tar.gz
rust-ca3d1010bb5f8ed6b7897c1d85f794857e00caa4.zip
Add `Iterator::array_chunks()`
-rw-r--r--library/core/src/iter/adapters/array_chunks.rs427
-rw-r--r--library/core/src/iter/adapters/mod.rs4
-rw-r--r--library/core/src/iter/mod.rs2
-rw-r--r--library/core/src/iter/traits/iterator.rs42
-rw-r--r--library/core/tests/iter/adapters/array_chunks.rs198
-rw-r--r--library/core/tests/iter/adapters/mod.rs23
-rw-r--r--library/core/tests/lib.rs1
7 files changed, 696 insertions, 1 deletions
diff --git a/library/core/src/iter/adapters/array_chunks.rs b/library/core/src/iter/adapters/array_chunks.rs
new file mode 100644
index 00000000000..f9c3f03cbb8
--- /dev/null
+++ b/library/core/src/iter/adapters/array_chunks.rs
@@ -0,0 +1,427 @@
+use crate::iter::{Fuse, FusedIterator, Iterator, TrustedLen};
+use crate::mem;
+use crate::mem::MaybeUninit;
+use crate::ops::{ControlFlow, Try};
+use crate::ptr;
+
+#[derive(Debug)]
+struct Remainder<T, const N: usize> {
+    array: [MaybeUninit<T>; N],
+    init: usize,
+}
+
+impl<T, const N: usize> Remainder<T, N> {
+    fn new() -> Self {
+        Self { array: MaybeUninit::uninit_array(), init: 0 }
+    }
+
+    unsafe fn with_init(array: [MaybeUninit<T>; N], init: usize) -> Self {
+        Self { array, init }
+    }
+
+    fn as_slice(&self) -> &[T] {
+        debug_assert!(self.init <= N);
+        // SAFETY: This raw slice will only contain the initialized objects
+        // within the buffer.
+        unsafe {
+            let slice = self.array.get_unchecked(..self.init);
+            MaybeUninit::slice_assume_init_ref(slice)
+        }
+    }
+
+    fn as_mut_slice(&mut self) -> &mut [T] {
+        debug_assert!(self.init <= N);
+        // SAFETY: This raw slice will only contain the initialized objects
+        // within the buffer.
+        unsafe {
+            let slice = self.array.get_unchecked_mut(..self.init);
+            MaybeUninit::slice_assume_init_mut(slice)
+        }
+    }
+}
+
+impl<T, const N: usize> Clone for Remainder<T, N>
+where
+    T: Clone,
+{
+    fn clone(&self) -> Self {
+        let mut new = Self::new();
+        // SAFETY: The new array is the same size and `init` is always less than
+        // or equal to `N`.
+        let this = unsafe { new.array.get_unchecked_mut(..self.init) };
+        MaybeUninit::write_slice_cloned(this, self.as_slice());
+        new.init = self.init;
+        new
+    }
+}
+
+impl<T, const N: usize> Drop for Remainder<T, N> {
+    fn drop(&mut self) {
+        // SAFETY: This raw slice will only contain the initialized objects
+        // within the buffer.
+        unsafe { ptr::drop_in_place(self.as_mut_slice()) }
+    }
+}
+
+/// An iterator over `N` elements of the iterator at a time.
+///
+/// The chunks do not overlap. If `N` does not divide the length of the
+/// iterator, then the last up to `N-1` elements will be omitted.
+///
+/// This `struct` is created by the [`array_chunks`][Iterator::array_chunks]
+/// method on [`Iterator`]. See its documentation for more.
+#[derive(Debug, Clone)]
+#[must_use = "iterators are lazy and do nothing unless consumed"]
+#[unstable(feature = "iter_array_chunks", reason = "recently added", issue = "none")]
+pub struct ArrayChunks<I: Iterator, const N: usize> {
+    iter: Fuse<I>,
+    remainder: Remainder<I::Item, N>,
+}
+
+impl<I, const N: usize> ArrayChunks<I, N>
+where
+    I: Iterator,
+{
+    pub(in crate::iter) fn new(iter: I) -> Self {
+        assert!(N != 0, "chunk size must be non-zero");
+        Self { iter: iter.fuse(), remainder: Remainder::new() }
+    }
+
+    /// Returns a reference to the remaining elements of the original iterator
+    /// that are not going to be returned by this iterator. The returned slice
+    /// has at most `N-1` elements.
+    #[unstable(feature = "iter_array_chunks", reason = "recently added", issue = "none")]
+    #[inline]
+    pub fn remainder(&self) -> &[I::Item] {
+        self.remainder.as_slice()
+    }
+
+    /// Returns a mutable reference to the remaining elements of the original
+    /// iterator that are not going to be returned by this iterator. The
+    /// returned slice has at most `N-1` elements.
+    #[unstable(feature = "iter_array_chunks", reason = "recently added", issue = "none")]
+    #[inline]
+    pub fn remainder_mut(&mut self) -> &mut [I::Item] {
+        self.remainder.as_mut_slice()
+    }
+}
+
+#[unstable(feature = "iter_array_chunks", reason = "recently added", issue = "none")]
+impl<I, const N: usize> Iterator for ArrayChunks<I, N>
+where
+    I: Iterator,
+{
+    type Item = [I::Item; N];
+
+    #[inline]
+    fn next(&mut self) -> Option<Self::Item> {
+        let mut array = MaybeUninit::uninit_array();
+        // SAFETY: `array` will still be valid if `guard` is dropped.
+        let mut guard = unsafe { FrontGuard::new(&mut array) };
+
+        for slot in array.iter_mut() {
+            match self.iter.next() {
+                Some(item) => {
+                    slot.write(item);
+                    guard.init += 1;
+                }
+                None => {
+                    if guard.init > 0 {
+                        let init = guard.init;
+                        mem::forget(guard);
+                        // SAFETY: `array` was initialized with `init` elements.
+                        self.remainder = unsafe { Remainder::with_init(array, init) };
+                    }
+                    return None;
+                }
+            }
+        }
+
+        mem::forget(guard);
+        // SAFETY: All elements of the array were populated in the loop above.
+        Some(unsafe { MaybeUninit::array_assume_init(array) })
+    }
+
+    #[inline]
+    fn size_hint(&self) -> (usize, Option<usize>) {
+        let (lower, upper) = self.iter.size_hint();
+        // Keep infinite iterator size hint lower bound as `usize::MAX`. This
+        // is required to implement `TrustedLen`.
+        if lower == usize::MAX {
+            return (lower, upper);
+        }
+        (lower / N, upper.map(|n| n / N))
+    }
+
+    #[inline]
+    fn count(self) -> usize {
+        self.iter.count() / N
+    }
+
+    fn try_fold<B, F, R>(&mut self, init: B, mut f: F) -> R
+    where
+        Self: Sized,
+        F: FnMut(B, Self::Item) -> R,
+        R: Try<Output = B>,
+    {
+        let mut array = MaybeUninit::uninit_array();
+        // SAFETY: `array` will still be valid if `guard` is dropped.
+        let mut guard = unsafe { FrontGuard::new(&mut array) };
+
+        let result = self.iter.try_fold(init, |mut acc, item| {
+            // SAFETY: `init` starts at 0, increases by one each iteration and
+            // is reset to 0 once it reaches N.
+            unsafe { array.get_unchecked_mut(guard.init) }.write(item);
+            guard.init += 1;
+            if guard.init == N {
+                guard.init = 0;
+                let array = mem::replace(&mut array, MaybeUninit::uninit_array());
+                // SAFETY: the condition above asserts that all elements are
+                // initialized.
+                let item = unsafe { MaybeUninit::array_assume_init(array) };
+                acc = f(acc, item)?;
+            }
+            R::from_output(acc)
+        });
+        match result.branch() {
+            ControlFlow::Continue(o) => {
+                if guard.init > 0 {
+                    let init = guard.init;
+                    mem::forget(guard);
+                    // SAFETY: `array` was initialized with `init` elements.
+                    self.remainder = unsafe { Remainder::with_init(array, init) };
+                }
+                R::from_output(o)
+            }
+            ControlFlow::Break(r) => R::from_residual(r),
+        }
+    }
+
+    fn fold<B, F>(self, init: B, mut f: F) -> B
+    where
+        Self: Sized,
+        F: FnMut(B, Self::Item) -> B,
+    {
+        let mut array = MaybeUninit::uninit_array();
+        // SAFETY: `array` will still be valid if `guard` is dropped.
+        let mut guard = unsafe { FrontGuard::new(&mut array) };
+
+        self.iter.fold(init, |mut acc, item| {
+            // SAFETY: `init` starts at 0, increases by one each iteration and
+            // is reset to 0 once it reaches N.
+            unsafe { array.get_unchecked_mut(guard.init) }.write(item);
+            guard.init += 1;
+            if guard.init == N {
+                guard.init = 0;
+                let array = mem::replace(&mut array, MaybeUninit::uninit_array());
+                // SAFETY: the condition above asserts that all elements are
+                // initialized.
+                let item = unsafe { MaybeUninit::array_assume_init(array) };
+                acc = f(acc, item);
+            }
+            acc
+        })
+    }
+}
+
+/// A guard for an array where elements are filled from the left.
+struct FrontGuard<T, const N: usize> {
+    /// A pointer to the array that is being filled. We need to use a raw
+    /// pointer here because of the lifetime issues in the fold implementations.
+    ptr: *mut T,
+    /// The number of *initialized* elements.
+    init: usize,
+}
+
+impl<T, const N: usize> FrontGuard<T, N> {
+    unsafe fn new(array: &mut [MaybeUninit<T>; N]) -> Self {
+        Self { ptr: MaybeUninit::slice_as_mut_ptr(array), init: 0 }
+    }
+}
+
+impl<T, const N: usize> Drop for FrontGuard<T, N> {
+    fn drop(&mut self) {
+        debug_assert!(self.init <= N);
+        // SAFETY: This raw slice will only contain the initialized objects
+        // within the buffer.
+        unsafe {
+            let slice = ptr::slice_from_raw_parts_mut(self.ptr, self.init);
+            ptr::drop_in_place(slice);
+        }
+    }
+}
+
+#[unstable(feature = "iter_array_chunks", reason = "recently added", issue = "none")]
+impl<I, const N: usize> DoubleEndedIterator for ArrayChunks<I, N>
+where
+    I: DoubleEndedIterator + ExactSizeIterator,
+{
+    #[inline]
+    fn next_back(&mut self) -> Option<Self::Item> {
+        // We are iterating from the back we need to first handle the remainder.
+        self.next_back_remainder()?;
+
+        let mut array = MaybeUninit::uninit_array();
+        // SAFETY: `array` will still be valid if `guard` is dropped.
+        let mut guard = unsafe { BackGuard::new(&mut array) };
+
+        for slot in array.iter_mut().rev() {
+            slot.write(self.iter.next_back()?);
+            guard.uninit -= 1;
+        }
+
+        mem::forget(guard);
+        // SAFETY: All elements of the array were populated in the loop above.
+        Some(unsafe { MaybeUninit::array_assume_init(array) })
+    }
+
+    fn try_rfold<B, F, R>(&mut self, init: B, mut f: F) -> R
+    where
+        Self: Sized,
+        F: FnMut(B, Self::Item) -> R,
+        R: Try<Output = B>,
+    {
+        // We are iterating from the back we need to first handle the remainder.
+        if self.next_back_remainder().is_none() {
+            return R::from_output(init);
+        }
+
+        let mut array = MaybeUninit::uninit_array();
+        // SAFETY: `array` will still be valid if `guard` is dropped.
+        let mut guard = unsafe { BackGuard::new(&mut array) };
+
+        self.iter.try_rfold(init, |mut acc, item| {
+            guard.uninit -= 1;
+            // SAFETY: `uninit` starts at N, decreases by one each iteration and
+            // is reset to N once it reaches 0.
+            unsafe { array.get_unchecked_mut(guard.uninit) }.write(item);
+            if guard.uninit == 0 {
+                guard.uninit = N;
+                let array = mem::replace(&mut array, MaybeUninit::uninit_array());
+                // SAFETY: the condition above asserts that all elements are
+                // initialized.
+                let item = unsafe { MaybeUninit::array_assume_init(array) };
+                acc = f(acc, item)?;
+            }
+            R::from_output(acc)
+        })
+    }
+
+    fn rfold<B, F>(mut self, init: B, mut f: F) -> B
+    where
+        Self: Sized,
+        F: FnMut(B, Self::Item) -> B,
+    {
+        // We are iterating from the back we need to first handle the remainder.
+        if self.next_back_remainder().is_none() {
+            return init;
+        }
+
+        let mut array = MaybeUninit::uninit_array();
+
+        // SAFETY: `array` will still be valid if `guard` is dropped.
+        let mut guard = unsafe { BackGuard::new(&mut array) };
+
+        self.iter.rfold(init, |mut acc, item| {
+            guard.uninit -= 1;
+            // SAFETY: `uninit` starts at N, decreases by one each iteration and
+            // is reset to N once it reaches 0.
+            unsafe { array.get_unchecked_mut(guard.uninit) }.write(item);
+            if guard.uninit == 0 {
+                guard.uninit = N;
+                let array = mem::replace(&mut array, MaybeUninit::uninit_array());
+                // SAFETY: the condition above asserts that all elements are
+                // initialized.
+                let item = unsafe { MaybeUninit::array_assume_init(array) };
+                acc = f(acc, item);
+            }
+            acc
+        })
+    }
+}
+
+impl<I, const N: usize> ArrayChunks<I, N>
+where
+    I: DoubleEndedIterator + ExactSizeIterator,
+{
+    #[inline]
+    fn next_back_remainder(&mut self) -> Option<()> {
+        // We use the `ExactSizeIterator` implementation of the underlying
+        // iterator to know how many remaining elements there are.
+        let rem = self.iter.len() % N;
+        if rem == 0 {
+            return Some(());
+        }
+
+        let mut array = MaybeUninit::uninit_array();
+
+        // SAFETY: The array will still be valid if `guard` is dropped and
+        // it is forgotten otherwise.
+        let mut guard = unsafe { FrontGuard::new(&mut array) };
+
+        // SAFETY: `rem` is in the range 1..N based on how it is calculated.
+        for slot in unsafe { array.get_unchecked_mut(..rem) }.iter_mut() {
+            slot.write(self.iter.next_back()?);
+            guard.init += 1;
+        }
+
+        let init = guard.init;
+        mem::forget(guard);
+        // SAFETY: `array` was initialized with exactly `init` elements.
+        self.remainder = unsafe {
+            array.get_unchecked_mut(..init).reverse();
+            Remainder::with_init(array, init)
+        };
+        Some(())
+    }
+}
+
+/// A guard for an array where elements are filled from the right.
+struct BackGuard<T, const N: usize> {
+    /// A pointer to the array that is being filled. We need to use a raw
+    /// pointer here because of the lifetime issues in the rfold implementations.
+    ptr: *mut T,
+    /// The number of *uninitialized* elements.
+    uninit: usize,
+}
+
+impl<T, const N: usize> BackGuard<T, N> {
+    unsafe fn new(array: &mut [MaybeUninit<T>; N]) -> Self {
+        Self { ptr: MaybeUninit::slice_as_mut_ptr(array), uninit: N }
+    }
+}
+
+impl<T, const N: usize> Drop for BackGuard<T, N> {
+    fn drop(&mut self) {
+        debug_assert!(self.uninit <= N);
+        // SAFETY: This raw slice will only contain the initialized objects
+        // within the buffer.
+        unsafe {
+            let ptr = self.ptr.offset(self.uninit as isize);
+            let slice = ptr::slice_from_raw_parts_mut(ptr, N - self.uninit);
+            ptr::drop_in_place(slice);
+        }
+    }
+}
+
+#[unstable(feature = "iter_array_chunks", reason = "recently added", issue = "none")]
+impl<I, const N: usize> FusedIterator for ArrayChunks<I, N> where I: FusedIterator {}
+
+#[unstable(feature = "iter_array_chunks", reason = "recently added", issue = "none")]
+impl<I, const N: usize> ExactSizeIterator for ArrayChunks<I, N>
+where
+    I: ExactSizeIterator,
+{
+    #[inline]
+    fn len(&self) -> usize {
+        self.iter.len() / N
+    }
+
+    #[inline]
+    fn is_empty(&self) -> bool {
+        self.iter.len() / N == 0
+    }
+}
+
+#[unstable(feature = "trusted_len", issue = "37572")]
+unsafe impl<I, const N: usize> TrustedLen for ArrayChunks<I, N> where I: TrustedLen {}
diff --git a/library/core/src/iter/adapters/mod.rs b/library/core/src/iter/adapters/mod.rs
index 916a26e2424..39e7ab87869 100644
--- a/library/core/src/iter/adapters/mod.rs
+++ b/library/core/src/iter/adapters/mod.rs
@@ -1,6 +1,7 @@
 use crate::iter::{InPlaceIterable, Iterator};
 use crate::ops::{ChangeOutputType, ControlFlow, FromResidual, NeverShortCircuit, Residual, Try};
 
+mod array_chunks;
 mod by_ref_sized;
 mod chain;
 mod cloned;
@@ -32,6 +33,9 @@ pub use self::{
     scan::Scan, skip::Skip, skip_while::SkipWhile, take::Take, take_while::TakeWhile, zip::Zip,
 };
 
+#[unstable(feature = "iter_array_chunks", reason = "recently added", issue = "none")]
+pub use self::array_chunks::ArrayChunks;
+
 #[unstable(feature = "std_internals", issue = "none")]
 pub use self::by_ref_sized::ByRefSized;
 
diff --git a/library/core/src/iter/mod.rs b/library/core/src/iter/mod.rs
index d5c6aed5b6c..d48e3a52c79 100644
--- a/library/core/src/iter/mod.rs
+++ b/library/core/src/iter/mod.rs
@@ -398,6 +398,8 @@ pub use self::traits::{
 
 #[stable(feature = "iter_zip", since = "1.59.0")]
 pub use self::adapters::zip;
+#[unstable(feature = "iter_array_chunks", reason = "recently added", issue = "none")]
+pub use self::adapters::ArrayChunks;
 #[unstable(feature = "std_internals", issue = "none")]
 pub use self::adapters::ByRefSized;
 #[stable(feature = "iter_cloned", since = "1.1.0")]
diff --git a/library/core/src/iter/traits/iterator.rs b/library/core/src/iter/traits/iterator.rs
index 275412b57b5..8bf41ca6f2a 100644
--- a/library/core/src/iter/traits/iterator.rs
+++ b/library/core/src/iter/traits/iterator.rs
@@ -5,7 +5,7 @@ use crate::ops::{ChangeOutputType, ControlFlow, FromResidual, Residual, Try};
 use super::super::try_process;
 use super::super::ByRefSized;
 use super::super::TrustedRandomAccessNoCoerce;
-use super::super::{Chain, Cloned, Copied, Cycle, Enumerate, Filter, FilterMap, Fuse};
+use super::super::{ArrayChunks, Chain, Cloned, Copied, Cycle, Enumerate, Filter, FilterMap, Fuse};
 use super::super::{FlatMap, Flatten};
 use super::super::{FromIterator, Intersperse, IntersperseWith, Product, Sum, Zip};
 use super::super::{
@@ -3316,6 +3316,46 @@ pub trait Iterator {
         Cycle::new(self)
     }
 
+    /// Returns an iterator over `N` elements of the iterator at a time.
+    ///
+    /// The chunks do not overlap. If `N` does not divide the length of the
+    /// iterator, then the last up to `N-1` elements will be omitted.
+    ///
+    /// # Panics
+    ///
+    /// Panics if `N` is 0.
+    ///
+    /// # Examples
+    ///
+    /// Basic usage:
+    ///
+    /// ```
+    /// #![feature(iter_array_chunks)]
+    ///
+    /// let mut iter = "lorem".chars().array_chunks();
+    /// assert_eq!(iter.next(), Some(['l', 'o']));
+    /// assert_eq!(iter.next(), Some(['r', 'e']));
+    /// assert_eq!(iter.next(), None);
+    /// assert_eq!(iter.remainder(), &['m']);
+    /// ```
+    ///
+    /// ```
+    /// #![feature(iter_array_chunks)]
+    ///
+    /// let data = [1, 1, 2, -2, 6, 0, 3, 1];
+    /// //          ^-----^  ^------^
+    /// for [x, y, z] in data.iter().array_chunks() {
+    ///     assert_eq!(x + y + z, 4);
+    /// }
+    /// ```
+    #[unstable(feature = "iter_array_chunks", reason = "recently added", issue = "none")]
+    fn array_chunks<const N: usize>(self) -> ArrayChunks<Self, N>
+    where
+        Self: Sized,
+    {
+        ArrayChunks::new(self)
+    }
+
     /// Sums the elements of an iterator.
     ///
     /// Takes each element, adds them together, and returns the result.
diff --git a/library/core/tests/iter/adapters/array_chunks.rs b/library/core/tests/iter/adapters/array_chunks.rs
new file mode 100644
index 00000000000..6845c94d364
--- /dev/null
+++ b/library/core/tests/iter/adapters/array_chunks.rs
@@ -0,0 +1,198 @@
+use core::cell::Cell;
+use core::iter::{self, Iterator};
+
+use super::*;
+
+#[test]
+fn test_iterator_array_chunks_infer() {
+    let xs = [1, 1, 2, -2, 6, 0, 3, 1];
+    for [a, b, c] in xs.iter().copied().array_chunks() {
+        assert_eq!(a + b + c, 4);
+    }
+}
+
+#[test]
+fn test_iterator_array_chunks_clone_and_drop() {
+    let count = Cell::new(0);
+    let mut it = (0..5).map(|_| CountDrop::new(&count)).array_chunks::<3>();
+
+    assert_eq!(it.by_ref().count(), 1);
+    assert_eq!(count.get(), 3);
+    assert_eq!(it.remainder().len(), 2);
+
+    let mut it2 = it.clone();
+    assert_eq!(count.get(), 3);
+    assert_eq!(it2.remainder().len(), 2);
+
+    drop(it);
+    assert_eq!(count.get(), 5);
+    assert_eq!(it2.remainder().len(), 2);
+    assert!(it2.next().is_none());
+
+    drop(it2);
+    assert_eq!(count.get(), 7);
+}
+
+#[test]
+fn test_iterator_array_chunks_remainder() {
+    let mut it = (0..11).array_chunks::<4>();
+    assert_eq!(it.remainder(), &[]);
+    assert_eq!(it.remainder_mut(), &[]);
+    assert_eq!(it.next(), Some([0, 1, 2, 3]));
+    assert_eq!(it.remainder(), &[]);
+    assert_eq!(it.remainder_mut(), &[]);
+    assert_eq!(it.next(), Some([4, 5, 6, 7]));
+    assert_eq!(it.remainder(), &[]);
+    assert_eq!(it.remainder_mut(), &[]);
+    assert_eq!(it.next(), None);
+    assert_eq!(it.next(), None);
+    assert_eq!(it.remainder(), &[8, 9, 10]);
+    assert_eq!(it.remainder_mut(), &[8, 9, 10]);
+}
+
+#[test]
+fn test_iterator_array_chunks_size_hint() {
+    let it = (0..6).array_chunks::<1>();
+    assert_eq!(it.size_hint(), (6, Some(6)));
+
+    let it = (0..6).array_chunks::<3>();
+    assert_eq!(it.size_hint(), (2, Some(2)));
+
+    let it = (0..6).array_chunks::<5>();
+    assert_eq!(it.size_hint(), (1, Some(1)));
+
+    let it = (0..6).array_chunks::<7>();
+    assert_eq!(it.size_hint(), (0, Some(0)));
+
+    let it = (1..).array_chunks::<2>();
+    assert_eq!(it.size_hint(), (usize::MAX, None));
+
+    let it = (1..).filter(|x| x % 2 != 0).array_chunks::<2>();
+    assert_eq!(it.size_hint(), (0, None));
+}
+
+#[test]
+fn test_iterator_array_chunks_count() {
+    let it = (0..6).array_chunks::<1>();
+    assert_eq!(it.count(), 6);
+
+    let it = (0..6).array_chunks::<3>();
+    assert_eq!(it.count(), 2);
+
+    let it = (0..6).array_chunks::<5>();
+    assert_eq!(it.count(), 1);
+
+    let it = (0..6).array_chunks::<7>();
+    assert_eq!(it.count(), 0);
+
+    let it = (0..6).filter(|x| x % 2 == 0).array_chunks::<2>();
+    assert_eq!(it.count(), 1);
+
+    let it = iter::empty::<i32>().array_chunks::<2>();
+    assert_eq!(it.count(), 0);
+
+    let it = [(); usize::MAX].iter().array_chunks::<2>();
+    assert_eq!(it.count(), usize::MAX / 2);
+}
+
+#[test]
+fn test_iterator_array_chunks_next_and_next_back() {
+    let mut it = (0..11).array_chunks::<3>();
+    assert_eq!(it.next(), Some([0, 1, 2]));
+    assert_eq!(it.next_back(), Some([6, 7, 8]));
+    assert_eq!(it.next(), Some([3, 4, 5]));
+    assert_eq!(it.next_back(), None);
+    assert_eq!(it.next(), None);
+    assert_eq!(it.next_back(), None);
+    assert_eq!(it.next(), None);
+    assert_eq!(it.remainder(), &[9, 10]);
+    assert_eq!(it.remainder_mut(), &[9, 10]);
+}
+
+#[test]
+fn test_iterator_array_chunks_rev_remainder() {
+    let mut it = (0..11).array_chunks::<4>();
+    {
+        let mut it = it.by_ref().rev();
+        assert_eq!(it.next(), Some([4, 5, 6, 7]));
+        assert_eq!(it.next(), Some([0, 1, 2, 3]));
+        assert_eq!(it.next(), None);
+        assert_eq!(it.next(), None);
+    }
+    assert_eq!(it.remainder(), &[8, 9, 10]);
+}
+
+#[test]
+fn test_iterator_array_chunks_try_fold() {
+    let count = Cell::new(0);
+    let mut it = (0..10).map(|_| CountDrop::new(&count)).array_chunks::<3>();
+    let result: Result<_, ()> = it.by_ref().try_fold(0, |acc, _item| Ok(acc + 1));
+    assert_eq!(result, Ok(3));
+    assert_eq!(it.remainder().len(), 1);
+    assert_eq!(count.get(), 9);
+    drop(it);
+    assert_eq!(count.get(), 10);
+
+    let count = Cell::new(0);
+    let mut it = (0..10).map(|_| CountDrop::new(&count)).array_chunks::<3>();
+    let result = it.by_ref().try_fold(0, |acc, _item| if acc < 2 { Ok(acc + 1) } else { Err(acc) });
+    assert_eq!(result, Err(2));
+    assert_eq!(it.remainder().len(), 0);
+    assert_eq!(count.get(), 9);
+    drop(it);
+    assert_eq!(count.get(), 9);
+}
+
+#[test]
+fn test_iterator_array_chunks_fold() {
+    let result = (1..11).array_chunks::<3>().fold(0, |acc, [a, b, c]| {
+        assert_eq!(acc + 1, a);
+        assert_eq!(acc + 2, b);
+        assert_eq!(acc + 3, c);
+        acc + 3
+    });
+    assert_eq!(result, 9);
+
+    let count = Cell::new(0);
+    let result =
+        (0..10).map(|_| CountDrop::new(&count)).array_chunks::<3>().fold(0, |acc, _item| acc + 1);
+    assert_eq!(result, 3);
+    assert_eq!(count.get(), 10);
+}
+
+#[test]
+fn test_iterator_array_chunks_try_rfold() {
+    let count = Cell::new(0);
+    let mut it = (0..10).map(|_| CountDrop::new(&count)).array_chunks::<3>();
+    let result: Result<_, ()> = it.try_rfold(0, |acc, _item| Ok(acc + 1));
+    assert_eq!(result, Ok(3));
+    assert_eq!(it.remainder().len(), 1);
+    assert_eq!(count.get(), 9);
+    drop(it);
+    assert_eq!(count.get(), 10);
+
+    let count = Cell::new(0);
+    let mut it = (0..10).map(|_| CountDrop::new(&count)).array_chunks::<3>();
+    let result = it.try_rfold(0, |acc, _item| if acc < 2 { Ok(acc + 1) } else { Err(acc) });
+    assert_eq!(result, Err(2));
+    assert_eq!(count.get(), 9);
+    drop(it);
+    assert_eq!(count.get(), 10);
+}
+
+#[test]
+fn test_iterator_array_chunks_rfold() {
+    let result = (1..11).array_chunks::<3>().rfold(0, |acc, [a, b, c]| {
+        assert_eq!(10 - (acc + 1), c);
+        assert_eq!(10 - (acc + 2), b);
+        assert_eq!(10 - (acc + 3), a);
+        acc + 3
+    });
+    assert_eq!(result, 9);
+
+    let count = Cell::new(0);
+    let result =
+        (0..10).map(|_| CountDrop::new(&count)).array_chunks::<3>().rfold(0, |acc, _item| acc + 1);
+    assert_eq!(result, 3);
+    assert_eq!(count.get(), 10);
+}
diff --git a/library/core/tests/iter/adapters/mod.rs b/library/core/tests/iter/adapters/mod.rs
index 567d9fe49ca..96539c0c394 100644
--- a/library/core/tests/iter/adapters/mod.rs
+++ b/library/core/tests/iter/adapters/mod.rs
@@ -1,3 +1,4 @@
+mod array_chunks;
 mod chain;
 mod cloned;
 mod copied;
@@ -183,3 +184,25 @@ impl Clone for CountClone {
         ret
     }
 }
+
+#[derive(Debug, Clone)]
+struct CountDrop<'a> {
+    dropped: bool,
+    count: &'a Cell<usize>,
+}
+
+impl<'a> CountDrop<'a> {
+    pub fn new(count: &'a Cell<usize>) -> Self {
+        Self { dropped: false, count }
+    }
+}
+
+impl Drop for CountDrop<'_> {
+    fn drop(&mut self) {
+        if self.dropped {
+            panic!("double drop");
+        }
+        self.dropped = true;
+        self.count.set(self.count.get() + 1);
+    }
+}
diff --git a/library/core/tests/lib.rs b/library/core/tests/lib.rs
index db94368f6e0..8b4838bb7bc 100644
--- a/library/core/tests/lib.rs
+++ b/library/core/tests/lib.rs
@@ -61,6 +61,7 @@
 #![feature(slice_partition_dedup)]
 #![feature(int_log)]
 #![feature(iter_advance_by)]
+#![feature(iter_array_chunks)]
 #![feature(iter_collect_into)]
 #![feature(iter_partition_in_place)]
 #![feature(iter_intersperse)]