Description
The Iterator::next_chunk
(see #98326) implementation for core::iter::Filter
does not gracefully drop the elements failing the predicate in the original iterator; instead it forgets them.
As an example, you can try this code:
#![feature(iter_next_chunk)]
struct LoudlyDropped(usize);
impl Drop for LoudlyDropped {
fn drop(&mut self) {
println!("No. {} getting dropped here!!!", self.0);
}
}
fn main() {
let v = (0..10).map(LoudlyDropped).collect::<Vec<_>>();
let _= v.into_iter().filter(|_| false).next_chunk::<1>();
}
I expected this program to generate the same message as the program without the last line in main
, that is:
#![feature(iter_next_chunk)]
struct LoudlyDropped(usize);
impl Drop for LoudlyDropped {
fn drop(&mut self) {
println!("No. {} getting dropped here!!!", self.0);
}
}
fn main() {
let _ = (0..10).map(LoudlyDropped).collect::<Vec<_>>();
}
However, instead, the first program doesn't print anything.
The origin of this behavior is trivial to find: in the lines 90--98 of the file formod core::iter::adapters::filter
of the current master (aabbf84), we have the following code (Github permalink), which is part of the implementation of next_chunk
:
let result = self.iter.try_for_each(|element| {
let idx = guard.initialized;
guard.initialized = idx + (self.predicate)(&element) as usize;
// SAFETY: Loop conditions ensure the index is in bounds.
unsafe { guard.array.get_unchecked_mut(idx) }.write(element);
if guard.initialized < N { ControlFlow::Continue(()) } else { ControlFlow::Break(()) }
});
This core::ptr::write
s each element that doesn't comply the element into the place out of the range of guard.initialized
.
The elements here, if not overwritten by this process, will be left undropped by the Drop
implementation of [T; N]::IntoIter
.
Meta
Playground version:
Build using the Nightly version: 1.81.0-nightly
(2024-06-22 3cb521a4344f0b556b81)