Fully consumed Vec::IntoIter retains an unreachable element-drop loop when used in a for loop
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 119k
- Forks
- 16.1k
- PR merge metrics
- PR metrics pending
Description
I tried this code:
#![crate_type = "lib"]
pub struct Bomb;
impl Drop for Bomb {
#[inline(never)]
fn drop(&mut self) {
panic!("dropped")
}
}
#[unsafe(no_mangle)]
pub fn via_for_loop(v: Vec<(usize, Option<Bomb>)>) -> usize {
let mut last = 0;
for (x, bomb) in v {
last = x;
std::mem::forget(bomb);
}
last
}
#[unsafe(no_mangle)]
pub fn via_for_each(v: Vec<(usize, Option<Bomb>)>) -> usize {
let mut last = 0;
v.into_iter().for_each(|(x, bomb)| {
last = x;
std::mem::forget(bomb);
});
last
}
Compiled with:
rustc -O -Cpanic=abort repro.rs
Every Option<Bomb> is unconditionally forgotten, and both functions fully consume the iterator. Therefore, neither function can call Bomb::drop.
I expected via_for_loop and via_for_each to have equivalent cleanup code: deallocate the vector allocation without checking or dropping any elements.
Instead, via_for_loop retains a loop over the supposedly remaining elements:
.LBB2_6:
cmp byte ptr [rdx], 0
jne .LBB2_7
add rdx, 16
dec rcx
jne .LBB2_6
...
.LBB2_7:
call qword ptr [rip + <example::Bomb as core::ops::drop::Drop>::drop@GOTPCREL]
The corresponding via_for_each function contains no element-drop loop or call to Bomb::drop; it only reads the last value and deallocates the allocation.
This appears to happen because the for loop drives Vec::IntoIter through repeated calls to next(). When next() returns None, the subsequent destructor still carries general-purpose logic for dropping remaining elements, even though reaching normal loop termination proves that there are none.
This is related to [PR #148486](https://github.com/rust-lang/rust/pull/148486), which explicitly forgets zero remaining elements after Vec::IntoIter::fold() completes. That fixes the fold/for_each path, but not the equivalent fully consumed for-loop path.
Meta
rustc 1.100.0-nightly (5db7f4be8 2026-09-01)
binary: rustc
commit-hash: 5db7f4be8a36c1b8ae19299469e2be2b0f052c21
commit-date: 2026-09-01
host: x86_64-unknown-linux-gnu
release: 1.100.0-nightly
LLVM version: 23.1.0
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Begin with the via_for_loop and via_for_each repro using the stated rustc optimization and panic settings, then compare their generated assembly. Read PR #148486 and investigate the Vec::IntoIter next() and cleanup behavior involved in the two paths. Done means the fully consumed for-loop path no longer retains the unreachable element-drop loop or Bomb::drop call.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- compilers, performance
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100