`Drop` トレイト

Essential
最終更新: タグ: Rust, メモリ

前提知識

Drop は Rust の自動クリーンアップを実現するトレイトだ。コンパイラは値のスコープが終わるたびに drop の呼び出しを挿入し、そこでリソース — ヒープメモリ、ファイルディスクリプタ(file descriptor)、ロック — が解放される。

トレイトの定義

pub trait Drop {
    fn drop(&mut self);
}

Drop を実装すると、値がスコープを外れたときにカスタムのクリーンアップを実行できる:

struct Guard {
    name: &'static str,
}

impl Drop for Guard {
    fn drop(&mut self) {
        println!("{} released", self.name);
    }
}

fn main() {
    let _a = Guard { name: "lock" };
    let _b = Guard { name: "file" };
} // "file released" が先に表示され、次に "lock released" が表示される

drop が実行されるタイミング

コンパイラは、値が宣言されたブロックの末尾、またはスコープを早期に抜ける returnbreak? のタイミングで drop を挿入する。

変数は宣言の逆順(LIFO)でドロップされる。上の例では _b_a の後に宣言されているため、_b が先にドロップされる。この順序により、ある値がドロップされるとき、構築中に使ったすべての値がまだ生きていることが保証される。

構造体(struct)のフィールドは、構造体自身の drop メソッドが実行された後に宣言順でドロップされる。

Drop を自分で実装することはほとんどない

ほとんどの型はカスタムの Drop を必要としない。コンパイラは各フィールドを順番に自動的にドロップする。Drop を自分で実装するのは、Rust 単独では管理できないリソース — 生のファイルディスクリプタ、C ライブラリのハンドル、GPU バッファ — を保持している場合に限られる。

最も一般的なケース — ヒープメモリ — については、Box<T>Vec<T>String がすでに Drop を実装してアロケーションを解放している。これらを使えば、クリーンアップは無償で手に入る。

drop を明示的に呼び出すことはできない

value.drop() を直接呼び出すとコンパイルエラーになる。drop がちょうど一度だけ実行されることを保証するために、コンパイラが実行タイミングを制御しなければならない。明示的な呼び出しを許可すると、その保証が崩れてしまう。

スコープが終わる前にリソースを解放したい場合は、フリー関数の std::mem::drop を使う:

let f = std::fs::File::open("data.txt")?;
// ... f を使う ...
std::mem::drop(f); // ここでファイルをクローズする
// この後で f を使おうとするとコンパイルエラー:値はムーブ済み

std::mem::drop は値の所有権を受け取る(関数内にムーブする)ことで、通常のドロップパスを起動し、所有権ルールを満たす。

DropCopy は相互排他

Copy 型は使われるたびにビット単位で複製される — コンパイラはどのコピーが「本物」かを追跡せずに自由にコピーを作る。Drop はちょうど一人の所有者がちょうど一回の drop 呼び出しを受け取ることを要求する。この二つの要件は両立しないため、両方を実装しようとする型はコンパイラに拒否される。