diff --git a/library/core/src/array/iter.rs b/library/core/src/array/iter.rs index 72a634443e877..302ff7d9e6472 100644 --- a/library/core/src/array/iter.rs +++ b/library/core/src/array/iter.rs @@ -84,6 +84,16 @@ impl IntoIter { IntoIterator::into_iter(array) } + /// Creates a new iterator from a partially initalized array. + /// + /// # Safety + /// + /// The caller must guarantee that all and only the `alive` elements of + /// `data` are initialized. + pub(crate) unsafe fn with_partial(data: [MaybeUninit; N], alive: Range) -> Self { + Self { data, alive } + } + /// Creates an iterator over the elements in a partially-initialized buffer. /// /// If you have a fully-initialized array, then use [`IntoIterator`]. 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 0000000000000..2ec1284c39406 --- /dev/null +++ b/library/core/src/iter/adapters/array_chunks.rs @@ -0,0 +1,362 @@ +use crate::array; +use crate::iter::{Fuse, FusedIterator, Iterator, TrustedLen}; +use crate::mem; +use crate::mem::MaybeUninit; +use crate::ops::{ControlFlow, Try}; +use crate::ptr; + +/// 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 { + iter: Fuse, + remainder: Option>, +} + +impl ArrayChunks +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: None } + } + + /// Returns an iterator over the remaining elements of the original iterator + /// that are not going to be returned by this iterator. The returned + /// iterator will yield at most `N-1` elements. + #[unstable(feature = "iter_array_chunks", reason = "recently added", issue = "none")] + #[inline] + pub fn into_remainder(self) -> Option> { + self.remainder + } +} + +#[unstable(feature = "iter_array_chunks", reason = "recently added", issue = "none")] +impl Iterator for ArrayChunks +where + I: Iterator, +{ + type Item = [I::Item; N]; + + #[inline] + fn next(&mut self) -> Option { + 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); + self.remainder = { + // SAFETY: `array` was initialized with `init` elements. + Some(unsafe { array::IntoIter::with_partial(array, 0..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) { + 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(&mut self, init: B, mut f: F) -> R + where + Self: Sized, + F: FnMut(B, Self::Item) -> R, + R: Try, + { + 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 = Some(unsafe { array::IntoIter::with_partial(array, 0..init) }); + } + R::from_output(o) + } + ControlFlow::Break(r) => R::from_residual(r), + } + } + + fn fold(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 { + /// 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 FrontGuard { + unsafe fn new(array: &mut [MaybeUninit; N]) -> Self { + Self { ptr: MaybeUninit::slice_as_mut_ptr(array), init: 0 } + } +} + +impl Drop for FrontGuard { + 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 DoubleEndedIterator for ArrayChunks +where + I: DoubleEndedIterator + ExactSizeIterator, +{ + #[inline] + fn next_back(&mut self) -> Option { + // 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(&mut self, init: B, mut f: F) -> R + where + Self: Sized, + F: FnMut(B, Self::Item) -> R, + R: Try, + { + // 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(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 ArrayChunks +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(); + Some(array::IntoIter::with_partial(array, 0..init)) + }; + Some(()) + } +} + +/// A guard for an array where elements are filled from the right. +struct BackGuard { + /// 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 BackGuard { + unsafe fn new(array: &mut [MaybeUninit; N]) -> Self { + Self { ptr: MaybeUninit::slice_as_mut_ptr(array), uninit: N } + } +} + +impl Drop for BackGuard { + 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 FusedIterator for ArrayChunks where I: FusedIterator {} + +#[unstable(feature = "iter_array_chunks", reason = "recently added", issue = "none")] +impl ExactSizeIterator for ArrayChunks +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 TrustedLen for ArrayChunks where I: TrustedLen {} diff --git a/library/core/src/iter/adapters/mod.rs b/library/core/src/iter/adapters/mod.rs index db8776ac7418d..ef281be1b5168 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::{ControlFlow, Try}; +mod array_chunks; mod chain; mod cloned; mod copied; @@ -31,6 +32,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; + #[stable(feature = "iter_cloned", since = "1.1.0")] pub use self::cloned::Cloned; diff --git a/library/core/src/iter/mod.rs b/library/core/src/iter/mod.rs index da459ed7c68f4..b98b6c3b6f855 100644 --- a/library/core/src/iter/mod.rs +++ b/library/core/src/iter/mod.rs @@ -393,6 +393,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; #[stable(feature = "iter_cloned", since = "1.1.0")] pub use self::adapters::Cloned; #[stable(feature = "iter_copied", since = "1.36.0")] diff --git a/library/core/src/iter/traits/iterator.rs b/library/core/src/iter/traits/iterator.rs index a8fe5f59bae01..a7b192b22e026 100644 --- a/library/core/src/iter/traits/iterator.rs +++ b/library/core/src/iter/traits/iterator.rs @@ -2,7 +2,7 @@ use crate::cmp::{self, Ordering}; use crate::ops::{ChangeOutputType, ControlFlow, FromResidual, Residual, Try}; 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::{ @@ -3089,6 +3089,48 @@ 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 and can be + /// retrieved from the [`.into_remainder()`][ArrayChunks::into_remainder] + /// function of the iterator. + /// + /// # 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.into_remainder().unwrap().as_slice(), &['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(self) -> ArrayChunks + 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 0000000000000..dbcfd4560289e --- /dev/null +++ b/library/core/tests/iter/adapters/array_chunks.rs @@ -0,0 +1,179 @@ +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); + let mut it2 = it.clone(); + assert_eq!(count.get(), 3); + assert_eq!(it.into_remainder().unwrap().len(), 2); + assert_eq!(count.get(), 5); + assert!(it2.next().is_none()); + assert_eq!(it2.into_remainder().unwrap().len(), 2); + assert_eq!(count.get(), 7); +} + +#[test] +fn test_iterator_array_chunks_remainder() { + let mut it = (0..11).array_chunks::<4>(); + assert_eq!(it.next(), Some([0, 1, 2, 3])); + assert_eq!(it.next(), Some([4, 5, 6, 7])); + assert_eq!(it.next(), None); + assert_eq!(it.into_remainder().unwrap().as_slice(), &[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::().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.into_remainder().unwrap().as_slice(), &[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.into_remainder().unwrap().as_slice(), &[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!(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!(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!(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 567d9fe49cade..96539c0c394e2 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, +} + +impl<'a> CountDrop<'a> { + pub fn new(count: &'a Cell) -> 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 a993def0a818e..85c2f8170da1b 100644 --- a/library/core/tests/lib.rs +++ b/library/core/tests/lib.rs @@ -62,6 +62,7 @@ #![feature(slice_partition_dedup)] #![feature(int_log)] #![feature(iter_advance_by)] +#![feature(iter_array_chunks)] #![feature(iter_partition_in_place)] #![feature(iter_intersperse)] #![feature(iter_is_partitioned)]