You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This is a tracking issue for the .next_chunk() and .next_chunk_back() method on iterators which allows you to advance the iterator N elements and return an array [T; N]
Public API
use core::array;traitIterator{typeItem;fnnext_chunk<constN:usize>(&mutself) -> Result<[Self::Item;N], array::IntoIter<Self::Item,N>>whereSelf:Sized;}traitDoubleEndedIterator{fnnext_chunk_back<constN:usize>(&mutself) -> Result<[Self::Item;N], array::IntoIter<Self::Item,N>>whereSelf:Sized;}
I can see that maybe being the case down the line unless we add something like const if or const match. But currently they just panic when N=0 so one can just put a simple if N == 0 { return Ok([]); } at the top which will easily get optimized out.
Edit: actually the above does NOT work and would need some kind of const if as well. I'll need to think about this.
It's possible to achieve a similar thing via specialization but it's not ideal.
I think for the name next_chunk it's slightly unfortunate that a "chunk" in this context is an array and not a slice, like slice.chunks(). This seems inconsistent and a clear potential source of confusion. next_array_chunk or similar removes any such ambiguities, and should be preferred, in my opinion.
If things are otherwise blocked on the handling of N == 0, would it be possible to delay this decision by panicking (at compile time?) for now, and then possibly allow it in the future? My understanding is that the case N == 0 is an edge case that would only come up in some unusual situations involving generics, and it seems unfortunate for such an edge case to delay stabilization for the common use cases of this method.
I think for the name next_chunk it's slightly unfortunate that a "chunk" in this context is an array and not a slice, like slice.chunks(). This seems inconsistent and a clear potential source of confusion. next_array_chunk or similar removes any such ambiguities, and should be preferred, in my opinion.
Whether a chunk is a slice or an array already is not consistent. E.g. slices already have as_chunks that returns a reference to arrays. I don't see much of a chance for confusion because -> [Self::Item] signatures are not possible and -> &[Self::Item] signatures would be neither practical nor useful to implement.
If things are otherwise blocked on the handling of N == 0, would it be possible to delay this decision by panicking (at compile time?) for now, and then possibly allow it in the future?
That would block the option of adding a where N > 0 bound once that's possible
Whether a chunk is a slice or an array already is not consistent. E.g. slices already have as_chunks that returns a reference to arrays. I don't see much of a chance for confusion because -> [Self::Item] signatures are not possible and -> &[Self::Item] signatures would be neither practical nor useful to implement.
Fair point, though I don't think any of these methods have been stabilized, unless I'm mistaken. as_chunks would probably also be better as as_array_chunks, for the same reason.
Actually, upon reviewing this post I see that last_chunk was recently made available in 1.77. Unfortunate. Oh well. In that case, you are correct, and that it's already unfortunately inconsistent.
In any case, the confusion I point to has to do with readability. By using clear and distinct naming, you do not have to stop and think through a logical argument like the one you suggest when scanning over the code.
I just don't see any downside in using a few more characters to avoid the ambiguity. But I also don't think we need to bikeshed this further.
That would block the option of adding a where N > 0 bound once that's possible
Thanks for pointing this out, that's what I was missing.
Before making this stable it would be useful to improve the documentation so that it provides an example with a loop. Now the examples have fixed amount of calls to next_chunk. I would think the more common use case is when not operating on a known input.
Just wondering if any of you also experienced some stack overflow errors using this feature?
I'm trying to use it (inside a Tokio task) but whenever I try to get a chunk of 475 or more element I get a stack overflow error. This is fine up to 474 elements.
I'm iterating over huge CSV files using the csv crate. I wanted to simplify a bit my code which was calling next() on my iterator and executing some code every 10_000 elements but I think I'll have to stick to my existing working code for now.
Just wanted to know if anyone else also stumbled on this issue?
The generated assembly is pretty bad. For some reason, the hot loop contains multiple checks that seem unnecessary.
Godbolt
That's using the default implementation which uses the internal array::iter_next_chunk, which I thought would be better optimized than that... CC @scottmcm didn't we have a trustedlen version of that at some point?
We can obviously do much better for slices.
I'm iterating over huge CSV files using the csv crate. I wanted to simplify a bit my code which was calling next() on my iterator and executing some code every 10_000 elements but I think I'll have to stick to my existing working code for now.
The use of Result return type seems a bit out of place since the Err is not a regular Error as one could expect. I think it would make more sense to return an enum with the states Chunk([u8;32]) and Remainder(iter) instead
The use of Result return type seems a bit out of place since the Err is not a regular Error as one could expect. I think it would make more sense to return an enum with the states Chunk([u8;32]) and Remainder(iter) instead
I suspect the fundamental problem here is that returning -> Option<[T; N]> (or result, or whatever that's not just the array or just a pointer) is rough from an ABI perspective. Something wanting a [T; N] will have space for that somewhere, but Option<[T; N]> is almost always bigger than that (particularly for the common cases of Option<[u8; N]>), and both end up with Memory ABIs, so they're returned via out pointer. But that means that next_chunk().unwrap() gets space for [T; N], but then needs to stack-allocate space for Option<[T; N]>, get the optional chunk into there, then copy the array into the return place. (That's part of how #107634 made map better.)
Where you can DerefMut<Target = [T]> the slice::Dropper<'_, T>, it drops the elements on drop, it's up to you to check how many were actually filled, etc. That way the return type is always the same size (it's just a wrapper on slice::IterMut) and ScalarPair so you don't have the ABI problems.
(Maybe also an array::Dropper so we could have fn fill_array(…) -> Result<array::Dropper<T, N>, slice::Dropper<T>>; for unwrapping or something. Dunno. Or something smart where the array version can unsize into the slice version, or something.)
That said, it really wants a promise that the returned dropper's pointers would be into the passed buffer, which would normally mean it'd need to be on an unsafe trait which is annoying for iterators since one would want this to always exist.
Maybe there's a way that fill_slice and fill_array would just be final fn, and there's other things that people can optionally implement that we'd call to populate the slice? Feels like anything that can do it materially faster is something with at least a helpful size_hint lower bound (since otherwise it'd need to go one-by-one and the default impl will be fine) so maybe there's a different version that can request the tristate: fill the whole thing, do nothing, or say you don't have enough size information to know. Then we just call that repeatedly -- which in practice on things like slice::Iterator is always exactly once.
Fundamentally the way to solve such issues is to change the way Result and Option are returned in the ABI. Specifically we want to have 2 separate return places, one for the "main" result (Ok/Some) and one for the rest of the enum. When the main result is returned via an out pointer, this allows the result to be directly placed into its final location. This along with the current work on MIR move optimization should allow these to optimize optimally.
I would much prefer this kind of ABI-level solution over making the API more complicated because of low-level implementation details.
Reacted by John Schug, Trevor Gross, Jérôme MEVEL and bendn
The other alternative here is to piggy-back on some future language feature for in-place initialization, which also has to address fallibility somehow. The downside of transparently making the current API lower to "out-ptr for Some/Ok payload" ABI is that it also fails transparently: whenever move elimination doesn't fire for any reason, or a pointer to an entire Result<[T; N], _> is required, codegen will be just as inefficient as today, without any clue at the source level.
In contrast, most in-place-init proposals have some explicit opt-in at the call site that guarantees no extra moves or temporaries. That's necessary for soundness in some use cases, but it's also useful for types (here: array lengths) large enough to overflow the stack. The downside is that there's some extra annotation burden, which may be a bad trade-off for an API that's partly about convenience. But depending on which (if any) approach is adopted, maybe the common case is succinct enough to be attractive for this API, too.
Are we able to add DoubleEndedIterator::next_chunk_back underneath this tracking issue? The person who made the ACP for this method and the original PR had it marked under #![feature(iter_next_chunk)] feature gated. I picked up the PR here to get it over the finish line due to inactivity.
Activity
pitaj commentedon May 25, 2023
I can see that maybe being the case down the line unless we add something like
const iforconst match. But currently they just panic when N=0so one can just put a simpleif N == 0 { return Ok([]); }at the top which will easily get optimized out.Edit: actually the above does NOT work and would need some kind of
const ifas well. I'll need to think about this.It's possible to achieve a similar thing via specialization but it's not ideal.
frewsxcv commentedon Jun 19, 2023
@rust-lang/libs Is there anything blocking a stabilization pull request? I'm happy to open that
Andlon commentedon May 18, 2024
I think for the name
next_chunkit's slightly unfortunate that a "chunk" in this context is an array and not a slice, likeslice.chunks(). This seems inconsistent and a clear potential source of confusion.next_array_chunkor similar removes any such ambiguities, and should be preferred, in my opinion.If things are otherwise blocked on the handling of
N == 0, would it be possible to delay this decision by panicking (at compile time?) for now, and then possibly allow it in the future? My understanding is that the caseN == 0is an edge case that would only come up in some unusual situations involving generics, and it seems unfortunate for such an edge case to delay stabilization for the common use cases of this method.the8472 commentedon May 18, 2024
Whether a chunk is a slice or an array already is not consistent. E.g. slices already have
as_chunksthat returns a reference to arrays. I don't see much of a chance for confusion because-> [Self::Item]signatures are not possible and-> &[Self::Item]signatures would be neither practical nor useful to implement.That would block the option of adding a
where N > 0bound once that's possibleAndlon commentedon May 18, 2024
Fair point, though I don't think any of these methods have been stabilized, unless I'm mistaken.
as_chunkswould probably also be better asas_array_chunks, for the same reason.Actually, upon reviewing this post I see that
last_chunkwas recently made available in1.77. Unfortunate. Oh well. In that case, you are correct, and that it's already unfortunately inconsistent.In any case, the confusion I point to has to do with readability. By using clear and distinct naming, you do not have to stop and think through a logical argument like the one you suggest when scanning over the code.
I just don't see any downside in using a few more characters to avoid the ambiguity. But I also don't think we need to bikeshed this further.
Thanks for pointing this out, that's what I was missing.
betelgeuse commentedon Jun 16, 2024
Before making this stable it would be useful to improve the documentation so that it provides an example with a loop. Now the examples have fixed amount of calls to next_chunk. I would think the more common use case is when not operating on a known input.
https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.next_chunk
Rollup merge of rust-lang#126879 - the8472:next-chunk-filter-drop, r=…
Rollup merge of rust-lang#126879 - the8472:next-chunk-filter-drop, r=…
Unrolled build for rust-lang#126879
jmevel commentedon Dec 8, 2024
Hi guys,
Just wondering if any of you also experienced some stack overflow errors using this feature?
I'm trying to use it (inside a Tokio task) but whenever I try to get a chunk of
475or more element I get a stack overflow error. This is fine up to474elements.I'm iterating over huge CSV files using the
csvcrate. I wanted to simplify a bit my code which was callingnext()on my iterator and executing some code every 10_000 elements but I think I'll have to stick to my existing working code for now.Just wanted to know if anyone else also stumbled on this issue?
Thanks
SuperSamus commentedon Dec 23, 2024
The generated assembly is pretty bad.
Godbolt
Benchmarking on my PC, the one that doesn't use
next_chunkis almost 3 times as fast.EDIT: Still an issue on 20/08/2026.
the8472 commentedon Dec 23, 2024
That's using the default implementation which uses the internal
array::iter_next_chunk, which I thought would be better optimized than that... CC @scottmcm didn't we have a trustedlen version of that at some point?We can obviously do much better for slices.
Arrays are usually stack-allocated if you don't box them. So using deep call chains with huge array arguments will lead to stack overflows unless most of the moves get optimized away. We'd need placement by return or changing the next_chunk signature to pass maybeuninit slice references, which would make this a lower-level method.
simonsso commentedon Aug 27, 2025
The use of Result return type seems a bit out of place since the Err is not a regular
Erroras one could expect. I think it would make more sense to return an enum with the statesChunk([u8;32])andRemainder(iter)insteadmax-heller commentedon Nov 1, 2025
The
Result-based signature feels similar toslice::binary_search() -> Result<usize, usize>andslice::contains() -> boolRollup merge of rust-lang#149131 - bend-n:optimize_slice_iter_next_ch…
Rollup merge of rust-lang#149131 - bend-n:optimize_slice_iter_next_ch…
Rollup merge of rust-lang#149131 - bend-n:optimize_slice_iter_next_ch…
Rollup merge of rust-lang#149131 - bend-n:optimize_slice_iter_next_ch…
Unrolled build for #149131
scottmcm commentedon Dec 1, 2025
(Commenting after the meeting that discussed #100450 (comment) )
I suspect the fundamental problem here is that returning
-> Option<[T; N]>(or result, or whatever that's not just the array or just a pointer) is rough from an ABI perspective. Something wanting a[T; N]will have space for that somewhere, butOption<[T; N]>is almost always bigger than that (particularly for the common cases ofOption<[u8; N]>), and both end up withMemoryABIs, so they're returned via out pointer. But that means thatnext_chunk().unwrap()gets space for[T; N], but then needs to stack-allocate space forOption<[T; N]>, get the optional chunk into there, then copy the array into the return place. (That's part of how #107634 mademapbetter.)Fixing that would mean, I think, an API like
Where you can
DerefMut<Target = [T]>theslice::Dropper<'_, T>, it drops the elements on drop, it's up to you to check how many were actually filled, etc. That way the return type is always the same size (it's just a wrapper onslice::IterMut) and ScalarPair so you don't have the ABI problems.(Maybe also an
array::Dropperso we could havefn fill_array(…) -> Result<array::Dropper<T, N>, slice::Dropper<T>>;forunwrappingor something. Dunno. Or something smart where the array version can unsize into the slice version, or something.)That said, it really wants a promise that the returned dropper's pointers would be into the passed
buffer, which would normally mean it'd need to be on anunsafe traitwhich is annoying for iterators since one would want this to always exist.Maybe there's a way that
fill_sliceandfill_arraywould just befinal fn, and there's other things that people can optionally implement that we'd call to populate the slice? Feels like anything that can do it materially faster is something with at least a helpful size_hint lower bound (since otherwise it'd need to go one-by-one and the default impl will be fine) so maybe there's a different version that can request the tristate: fill the whole thing, do nothing, or say you don't have enough size information to know. Then we just call that repeatedly -- which in practice on things likeslice::Iteratoris always exactly once.Amanieu commentedon Dec 2, 2025
Fundamentally the way to solve such issues is to change the way
ResultandOptionare returned in the ABI. Specifically we want to have 2 separate return places, one for the "main" result (Ok/Some) and one for the rest of the enum. When the main result is returned via an out pointer, this allows the result to be directly placed into its final location. This along with the current work on MIR move optimization should allow these to optimize optimally.I would much prefer this kind of ABI-level solution over making the API more complicated because of low-level implementation details.
hanna-kruppe commentedon Dec 24, 2025
The other alternative here is to piggy-back on some future language feature for in-place initialization, which also has to address fallibility somehow. The downside of transparently making the current API lower to "out-ptr for Some/Ok payload" ABI is that it also fails transparently: whenever move elimination doesn't fire for any reason, or a pointer to an entire
Result<[T; N], _>is required, codegen will be just as inefficient as today, without any clue at the source level.In contrast, most in-place-init proposals have some explicit opt-in at the call site that guarantees no extra moves or temporaries. That's necessary for soundness in some use cases, but it's also useful for types (here: array lengths) large enough to overflow the stack. The downside is that there's some extra annotation burden, which may be a bad trade-off for an API that's partly about convenience. But depending on which (if any) approach is adopted, maybe the common case is succinct enough to be attractive for this API, too.
Rollup merge of rust-lang#149131 - bend-n:optimize_slice_iter_next_ch…
Rollup merge of rust-lang#150230 - bend-n:trusted_len_spec_for_iter_n…
Rollup merge of rust-lang#150230 - bend-n:trusted_len_spec_for_iter_n…
Unrolled build for #150230
asder8215 commentedon May 19, 2026
Are we able to add
DoubleEndedIterator::next_chunk_backunderneath this tracking issue? The person who made the ACP for this method and the original PR had it marked under#![feature(iter_next_chunk)]feature gated. I picked up the PR here to get it over the finish line due to inactivity.Rollup merge of rust-lang#156737 - asder8215:double_ended_iterator_ne…
Auto merge of #156737 - asder8215:double_ended_iterator_next_chunk_ba…
Rollup merge of rust-lang#156737 - asder8215:double_ended_iterator_ne…
Unrolled build for #156737
aapoalas commentedon Jul 3, 2026
Added
.next_chunk_back()to the tracking issue.asder8215 commentedon Jul 3, 2026
@aapoalas to note,
.next_chunk_back()should be underneath theDoubleEndedIteratortrait notIteratortrait.giang299 commentedon Jul 11, 2026
IT MUST BE next_chunk()
next_chunk is amz naming. we already have a Vec::chunks(),..etc before, it's universal.