---
minutes: 15
---

# Drop Bombs: Enforcing API Correctness

Use `Drop` to enforce invariants and detect incorrect API usage. A "drop bomb"
panics if a value is dropped without being explicitly finalized.

This pattern is often used when the finalizing operation (like `commit()` or
`rollback()`) needs to return a `Result`, which cannot be done from `Drop`.

```rust,editable
use std::io::{self, Write};

struct Transaction {
    active: bool,
}

impl Transaction {
    fn start() -> Self {
        Self { active: true }
    }

    fn commit(mut self) -> io::Result<()> {
        writeln!(io::stdout(), "COMMIT")?;
        self.active = false;
        Ok(())
    }
}

impl Drop for Transaction {
    fn drop(&mut self) {
        if self.active {
            panic!("Transaction dropped without commit!");
        }
    }
}

fn main() -> io::Result<()> {
    let tx = Transaction::start();
    // Use `tx` to build the transaction, then commit it.
    // Comment out the call to `commit` to see the panic.
    tx.commit()?;
    Ok(())
}
```

<details>

- In some systems, a value must be finalized by a specific API before it is
  dropped.

  For example, a `Transaction` might need to be committed or rolled back.

- A drop bomb ensures that a value like `Transaction` cannot be silently dropped
  in an unfinished state. The destructor panics if the transaction has not been
  explicitly finalized (for example, with `commit()`).

- The finalizing operation (such as `commit()`) usually takes `self` by value.
  This ensures that once the transaction is finalized, the original object can
  no longer be used.

- A common reason to use this pattern is when cleanup cannot be done in `Drop`,
  either because it is fallible or asynchronous.

- This pattern is appropriate even in public APIs. It can help users catch bugs
  early when they forget to explicitly finalize a transactional object.

- If cleanup can safely happen in `Drop`, some APIs choose to panic only in
  debug builds. Whether this is appropriate depends on the guarantees your API
  must enforce.

- Panicking in release builds is reasonable when silent misuse would cause major
  correctness or security problems.

- Question: Why do we need an `active` flag inside `Transaction`? Why can't
  `drop()` panic unconditionally?

  Expected answer: `commit()` takes `self` by value and runs `drop()`, which
  would panic.

## More to explore

Several related patterns help enforce correct teardown or prevent accidental
drops.

- The [`drop_bomb` crate](https://docs.rs/drop_bomb/latest/drop_bomb/): A small
  utility that panics if dropped unless explicitly defused with `.defuse()`.
  Comes with a `DebugDropBomb` variant that only activates in debug builds.

</details>
