Skip to content

Panics in destructors can cause the tail expression of a block to be leaked #47949

Open

Description

@arielb1

View all comments

STR

struct NoisyDrop;
impl Drop for NoisyDrop {
    fn drop(&mut self) {
        println!("dropping a NoisyDrop");
    }
}

impl NoisyDrop {
    fn new() -> Self {
        println!("creating a NoisyDrop");
        NoisyDrop
    }
}

struct PanickyDrop;
impl Drop for PanickyDrop {
    fn drop(&mut self) {
        panic!()
    }
}

fn foo() -> NoisyDrop {
    let p = PanickyDrop;
    NoisyDrop::new()
}

fn main() {
    foo();
}

Expected Result

"creating a NoisyDrop" and "dropping a NoisyDrop" should appear the same number of times

Actual Result

creating a NoisyDrop
thread 'main' panicked at 'explicit panic', src/main.rs:18:8
note: Run with `RUST_BACKTRACE=1` for a backtrace.

The destructor is ignored.

Activity

  1. added
    I-unsoundIssue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness
    on Feb 1, 2018
  2. jonas-schievink commented on Feb 1, 2018

    @jonas-schievink
    Contributor

    Heh, this even happens on Rust 1.0

  3. arielb1 commented on Feb 2, 2018

    @arielb1
    ContributorAuthor

    @jonas-schievink

    Rust 1.0 is far worse than this - it leaks everything when a destructor panics.

  4. solb commented on Mar 8, 2018

    @solb

    @arielb1, could you explain why this is tagged as a soundness bug? The language makes no guarantee that destructors will ever be invoked.

  5. added
    C-enhancementCategory: An issue proposing an enhancement or a PR with one.
    T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.
    on Apr 23, 2018
  6. removed
    I-unsoundIssue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness
    on Oct 2, 2018
  7. gnzlbg commented on May 7, 2019

    @gnzlbg
    Contributor

    ping @rust-lang/compiler this should be triagged, even if it is not a soundness bug, unwinding working properly is something that a lot of code relies on.

  8. added
    I-unsoundIssue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness
    and removed
    I-unsoundIssue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness
    on May 7, 2019
  9. nikomatsakis commented on May 7, 2019

    @nikomatsakis
    Contributor

    So, to be clear, it's not that we leak all destructors when panic in drop occurs. For example, this function drops both p and n as expected:

    fn foo() {
        let n = NoisyDrop::new();
        let p = PanickyDrop;
    }

    The specific scenario here is that:

    • we are in the middle of returning the NoisyDrop to our caller (so our caller "owns it")
    • but we panic before our return completes (so they don't realize they own it)

    I don't think it's quite right to say that "we make no guarantee that destructors will ever be invoked". We don't guarantee that all destructors are always invoked, but we do guarantee some things: for example, that if you have a local variable whose scope includes a call, it will be dropped when the call panics (here, n would be dropped):

    let n = ...;
    foo();

    So the question is what we should guarantee in this particular scenario. Perhaps the answer is that we should just be very clear to document the current behavior, given how long it has existed.

  10. 63 remaining items

  11. added a commit that references this issue on Jun 4, 2024
    1c34390
  12. added 3 commits that reference this issue on Jun 14, 2024
    7123552
    026e194
    8585d8c
  13. added a commit that references this issue on Jul 15, 2024
    b905bf4
  14. theemathas commented on May 23, 2026

    @theemathas
    Contributor

    Possibly related to #156713.

  15. changed the title [-]Panics in destructors can cause the return value to be leaked[/-] [+]Panics in destructors can cause the tail expression of a block to be leaked[/+] on May 23, 2026
  16. theemathas commented on May 24, 2026

    @theemathas
    Contributor

    A leak can also happen with a closure that panics upon drop, even without any relevant blocks.

    struct LoudDrop;
    impl Drop for LoudDrop {
        fn drop(&mut self) {
            println!("dropped");
        }
    }
    fn new() -> LoudDrop {
        println!("created");
        LoudDrop
    }
    
    struct PanicOnDrop;
    impl Drop for PanicOnDrop {
        fn drop(&mut self) {
            panic!()
        }
    }
    
    fn foo(f: impl FnOnce(LoudDrop) -> LoudDrop) {
        f(new());
    }
    
    fn main() {
        let x = PanicOnDrop;
        foo(move |y| (&x, y).1);
    }

    The above code prints "created" but not "drop".

  17. added
    I-memleakIssue: Runtime memory leak without `mem::forget`.
    on Oct 25, 2026
  18. added
    C-bugCategory: This is a bug.
    and removed
    C-enhancementCategory: An issue proposing an enhancement or a PR with one.
    on Oct 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

A-destructorsArea: Destructors (`Drop`, …)A-panicArea: Panicking machineryC-bugCategory: This is a bug.I-memleakIssue: Runtime memory leak without `mem::forget`.I-unsoundIssue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/SoundnessP-highHigh priorityT-compilerRelevant to the compiler team, which will review and decide on the PR/issue.T-langRelevant to the language team

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions