Drop はスキップされることがある

デストラクタが実行されない場合があります。

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

#[derive(Debug)]
struct OwnedFd(i32);

impl Drop for OwnedFd {
    fn drop(&mut self) {
        println!("OwnedFd::drop() called with raw fd: {:?}", self.0);
    }
}

impl Drop for TmpFile {
    fn drop(&mut self) {
        println!("TmpFile::drop() called with owned fd: {:?}", self.0);
        // libc::unlink("/tmp/file")
        // panic!("TmpFile::drop() panics");
    }
}

#[derive(Debug)]
struct TmpFile(OwnedFd);

impl TmpFile {
    fn open() -> Self {
        Self(OwnedFd(2))
    }

    fn close(&self) {
        panic!("TmpFile::close(): not implemented yet");
    }
}

fn main() {
    let owned_fd = OwnedFd(1);

    let file = TmpFile::open();

    std::process::exit(0);

    // std::mem::forget(file);

    // file.close();

    let _ = owned_fd;
}
  • Drop は必ず実行されるとは限りません。drop がスキップされるケースはいくつもあります。たとえば、プログラムがクラッシュしたり終了したり、Drop 実装を持つ値がリークされたりする場合です。

  • std::process::exit を呼び出すバージョンでは、exit()drop() メソッドが呼ばれる機会を一切与えずにプロセスを即座に終了させるため、TmpFile::drop() は決して実行されません。

    • clippy::exit lint を deny することで、exit の偶発的な使用を防げます。
  • std::process::exit(0) の行を削除すると、この単純なケースでは各 drop() メソッドが順番に実行されます。

  • std::mem::forget の呼び出しのコメントを外してみてください。何が起こると思いますか。

    mem::forget() は所有権を受け取り、デストラクタ Drop::drop() を実行せずに値 file を「忘れ」ます。owned_fd のデストラクタは引き続き実行されます。

  • mem::forget() の呼び出しを削除し、その下の file.close() 呼び出しのコメントを外してください。今度は何が起こると思いますか。

    デフォルトの panic = "unwind" 設定では、panic が main で始まった場合でも、スタックは引き続きアンワインドされ、デストラクタは実行されます。

    • panic = "abort" では、デストラクタは一切実行されません。
  • 最後のステップとして、TmpFile::drop() 内の panic! のコメントを外して実行してください。クラスに質問してみましょう: abort の前にどのデストラクタが実行されますか。

    二重 panic の後は、Rust は残りのデストラクタが実行されることをもはや保証しません。

    • すでに進行中だった一部のクリーンアップは完了することがあります(たとえば、現在 drop 中の値のフィールドデストラクタなど)。
    • しかし、アンワインド経路の後続で予定されていたものは、完全にスキップされる可能性があります。
    • これが、重要な外部クリーンアップを drop() だけに頼ることはできず、また二重 panic による abort でそれ以降のデストラクタがまったく実行されないとも仮定できない、と言う理由です。
  • 一部の言語では、デストラクタ内での例外が禁止または制限されています。Rust は Drop::drop 内での panic を許可していますが、これはほとんどの場合よい考えではありません。アンワインドを妨げ、予測不能なクリーンアップにつながる可能性があるからです。drop bomb のような非常に特別な必要がある場合を除き、避けるのが最善です。

  • Drop はプロセスのスコープ内でリソースをクリーンアップするのには適していますが、プロセスの外部で何かが起こることを強く保証するための適切な手段ではありません(たとえば、ローカルディスク上や分散システム内の別のサービス上など)。

  • たとえば、drop() で一時ファイルを削除するのはおもちゃの例では問題ありませんが、実際のプログラムでは temp file reaper のような外部クリーンアップ機構が依然として必要になります。

  • 対照的に、drop() による mutex のアンロックは信頼できます。これはプロセスローカルなリソースだからです。drop() がスキップされて mutex がロックされたままになっても、プロセスの外部に永続的な影響はありません。