rust-lang / rust-lang/rust

Fully consumed Vec::IntoIter retains an unreachable element-drop loop when used in a for loop

Open
#162,219 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

C-bug needs-triage
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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.