Skip to content

Commit 1aabd8a

Browse files
committed
Auto merge of #93700 - rossmacarthur:ft/iter-next-chunk, r=m-ou-se
Add `Iterator::next_chunk` See also #92393 ### Prior art - [`Itertools::next_tuple()`](https://docs.rs/itertools/latest/itertools/trait.Itertools.html#method.next_tuple) ### Unresolved questions - Should we also add `next_chunk_back` to `DoubleEndedIterator`? - Should we rather call this `next_array()` or `next_array_chunk`?
2 parents e02d645 + bbdff1f commit 1aabd8a

File tree

4 files changed

+100
-31
lines changed

4 files changed

+100
-31
lines changed

Diff for: library/core/src/array/mod.rs

+48-31
Original file line numberDiff line numberDiff line change
@@ -780,24 +780,27 @@ where
780780
}
781781

782782
/// Pulls `N` items from `iter` and returns them as an array. If the iterator
783-
/// yields fewer than `N` items, `None` is returned and all already yielded
784-
/// items are dropped.
783+
/// yields fewer than `N` items, `Err` is returned containing an iterator over
784+
/// the already yielded items.
785785
///
786786
/// Since the iterator is passed as a mutable reference and this function calls
787787
/// `next` at most `N` times, the iterator can still be used afterwards to
788788
/// retrieve the remaining items.
789789
///
790790
/// If `iter.next()` panicks, all items already yielded by the iterator are
791791
/// dropped.
792-
fn try_collect_into_array<I, T, R, const N: usize>(iter: &mut I) -> Option<R::TryType>
792+
#[inline]
793+
fn try_collect_into_array<I, T, R, const N: usize>(
794+
iter: &mut I,
795+
) -> Result<R::TryType, IntoIter<T, N>>
793796
where
794797
I: Iterator,
795798
I::Item: Try<Output = T, Residual = R>,
796799
R: Residual<[T; N]>,
797800
{
798801
if N == 0 {
799802
// SAFETY: An empty array is always inhabited and has no validity invariants.
800-
return unsafe { Some(Try::from_output(mem::zeroed())) };
803+
return Ok(Try::from_output(unsafe { mem::zeroed() }));
801804
}
802805

803806
struct Guard<'a, T, const N: usize> {
@@ -821,35 +824,49 @@ where
821824
let mut array = MaybeUninit::uninit_array::<N>();
822825
let mut guard = Guard { array_mut: &mut array, initialized: 0 };
823826

824-
while let Some(item_rslt) = iter.next() {
825-
let item = match item_rslt.branch() {
826-
ControlFlow::Break(r) => {
827-
return Some(FromResidual::from_residual(r));
827+
for _ in 0..N {
828+
match iter.next() {
829+
Some(item_rslt) => {
830+
let item = match item_rslt.branch() {
831+
ControlFlow::Break(r) => {
832+
return Ok(FromResidual::from_residual(r));
833+
}
834+
ControlFlow::Continue(elem) => elem,
835+
};
836+
837+
// SAFETY: `guard.initialized` starts at 0, is increased by one in the
838+
// loop and the loop is aborted once it reaches N (which is
839+
// `array.len()`).
840+
unsafe {
841+
guard.array_mut.get_unchecked_mut(guard.initialized).write(item);
842+
}
843+
guard.initialized += 1;
844+
}
845+
None => {
846+
let alive = 0..guard.initialized;
847+
mem::forget(guard);
848+
// SAFETY: `array` was initialized with exactly `initialized`
849+
// number of elements.
850+
return Err(unsafe { IntoIter::new_unchecked(array, alive) });
828851
}
829-
ControlFlow::Continue(elem) => elem,
830-
};
831-
832-
// SAFETY: `guard.initialized` starts at 0, is increased by one in the
833-
// loop and the loop is aborted once it reaches N (which is
834-
// `array.len()`).
835-
unsafe {
836-
guard.array_mut.get_unchecked_mut(guard.initialized).write(item);
837-
}
838-
guard.initialized += 1;
839-
840-
// Check if the whole array was initialized.
841-
if guard.initialized == N {
842-
mem::forget(guard);
843-
844-
// SAFETY: the condition above asserts that all elements are
845-
// initialized.
846-
let out = unsafe { MaybeUninit::array_assume_init(array) };
847-
return Some(Try::from_output(out));
848852
}
849853
}
850854

851-
// This is only reached if the iterator is exhausted before
852-
// `guard.initialized` reaches `N`. Also note that `guard` is dropped here,
853-
// dropping all already initialized elements.
854-
None
855+
mem::forget(guard);
856+
// SAFETY: All elements of the array were populated in the loop above.
857+
let output = unsafe { MaybeUninit::array_assume_init(array) };
858+
Ok(Try::from_output(output))
859+
}
860+
861+
/// Returns the next chunk of `N` items from the iterator or errors with an
862+
/// iterator over the remainder. Used for `Iterator::next_chunk`.
863+
#[inline]
864+
pub(crate) fn iter_next_chunk<I, const N: usize>(
865+
iter: &mut I,
866+
) -> Result<[I::Item; N], IntoIter<I::Item, N>>
867+
where
868+
I: Iterator,
869+
{
870+
let mut map = iter.map(NeverShortCircuit);
871+
try_collect_into_array(&mut map).map(|NeverShortCircuit(arr)| arr)
855872
}

Diff for: library/core/src/iter/traits/iterator.rs

+42
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::array;
12
use crate::cmp::{self, Ordering};
23
use crate::ops::{ChangeOutputType, ControlFlow, FromResidual, Residual, Try};
34

@@ -102,6 +103,47 @@ pub trait Iterator {
102103
#[stable(feature = "rust1", since = "1.0.0")]
103104
fn next(&mut self) -> Option<Self::Item>;
104105

106+
/// Advances the iterator and returns an array containing the next `N` values.
107+
///
108+
/// If there are not enough elements to fill the array then `Err` is returned
109+
/// containing an iterator over the remaining elements.
110+
///
111+
/// # Examples
112+
///
113+
/// Basic usage:
114+
///
115+
/// ```
116+
/// #![feature(iter_next_chunk)]
117+
///
118+
/// let mut iter = "lorem".chars();
119+
///
120+
/// assert_eq!(iter.next_chunk().unwrap(), ['l', 'o']); // N is inferred as 2
121+
/// assert_eq!(iter.next_chunk().unwrap(), ['r', 'e', 'm']); // N is inferred as 3
122+
/// assert_eq!(iter.next_chunk::<4>().unwrap_err().as_slice(), &[]); // N is explicitly 4
123+
/// ```
124+
///
125+
/// Split a string and get the first three items.
126+
///
127+
/// ```
128+
/// #![feature(iter_next_chunk)]
129+
///
130+
/// let quote = "not all those who wander are lost";
131+
/// let [first, second, third] = quote.split_whitespace().next_chunk().unwrap();
132+
/// assert_eq!(first, "not");
133+
/// assert_eq!(second, "all");
134+
/// assert_eq!(third, "those");
135+
/// ```
136+
#[inline]
137+
#[unstable(feature = "iter_next_chunk", reason = "recently added", issue = "98326")]
138+
fn next_chunk<const N: usize>(
139+
&mut self,
140+
) -> Result<[Self::Item; N], array::IntoIter<Self::Item, N>>
141+
where
142+
Self: Sized,
143+
{
144+
array::iter_next_chunk(self)
145+
}
146+
105147
/// Returns the bounds on the remaining length of the iterator.
106148
///
107149
/// Specifically, `size_hint()` returns a tuple where the first element

Diff for: library/core/tests/iter/traits/iterator.rs

+9
Original file line numberDiff line numberDiff line change
@@ -575,6 +575,15 @@ fn iter_try_collect_uses_try_fold_not_next() {
575575
// validation is just that it didn't panic.
576576
}
577577

578+
#[test]
579+
fn test_next_chunk() {
580+
let mut it = 0..12;
581+
assert_eq!(it.next_chunk().unwrap(), [0, 1, 2, 3]);
582+
assert_eq!(it.next_chunk().unwrap(), []);
583+
assert_eq!(it.next_chunk().unwrap(), [4, 5, 6, 7, 8, 9]);
584+
assert_eq!(it.next_chunk::<4>().unwrap_err().as_slice(), &[10, 11]);
585+
}
586+
578587
// just tests by whether or not this compiles
579588
fn _empty_impl_all_auto_traits<T>() {
580589
use std::panic::{RefUnwindSafe, UnwindSafe};

Diff for: library/core/tests/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767
#![feature(iter_partition_in_place)]
6868
#![feature(iter_intersperse)]
6969
#![feature(iter_is_partitioned)]
70+
#![feature(iter_next_chunk)]
7071
#![feature(iter_order_by)]
7172
#![feature(iterator_try_collect)]
7273
#![feature(iterator_try_reduce)]

0 commit comments

Comments
 (0)