Drop Bomb: API の正しさを強制する
不変条件を強制し、API の誤った使い方を検出するには Drop を使います。 「drop bomb」は、値が明示的にファイナライズされないままドロップされると panic します。
このパターンは、ファイナライズ操作(commit() や rollback() など)が Result を返す必要がある場合によく使われます。これは Drop からは 行えません。
// Copyright 2025 Google LLC // SPDX-License-Identifier: Apache-2.0 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(); // `tx` を使ってトランザクションを構築し、その後コミットします。 // panic を確認するには、`commit` の呼び出しをコメントアウトしてください。 tx.commit()?; Ok(()) }
-
一部のシステムでは、値はドロップされる前に特定の API によって ファイナライズされなければなりません。
たとえば、
Transactionはコミットまたはロールバックする必要がある 場合があります。 -
drop bomb は、
Transactionのような値が未完了の状態のまま黙って ドロップされないことを保証します。トランザクションが明示的に ファイナライズされていない場合(たとえばcommit()していない場合)、 デストラクタは panic します。 -
ファイナライズ操作(
commit()など)は通常、selfを値で受け取ります。 これにより、トランザクションがファイナライズされた後は、元のオブジェクトを もう使えなくなります。 -
このパターンを使う一般的な理由は、クリーンアップを
Dropで行えない 場合です。失敗する可能性がある、または非同期であることがその理由です。 -
このパターンは公開 API でも適切です。トランザクションオブジェクトを 明示的にファイナライズし忘れたときに、ユーザーが早い段階でバグを 見つける助けになります。
-
クリーンアップを
Dropで安全に行えるなら、デバッグビルドでのみ panic することを選ぶ API もあります。これが適切かどうかは、API が強制しなければ ならない保証に依存します。 -
誤用が黙って見過ごされることで重大な正しさやセキュリティ上の問題が 生じる場合、リリースビルドで panic するのは妥当です。
-
質問: なぜ
Transactionの中にactiveフラグが必要なのでしょうか。なぜdrop()は無条件に panic してはいけないのでしょうか。期待される答え:
commit()はselfを値で受け取り、その際にdrop()が 実行されるため、panic してしまいます。
さらに調べる
適切な後始末を強制したり、誤ってドロップしてしまうことを防いだりする 関連パターンがいくつかあります。
drop_bombcrate:.defuse()で明示的に無効化しない限り、ドロップされると panic する小さな ユーティリティです。デバッグビルドでのみ有効になるDebugDropBombバリアントも付属しています。