`Drop` トレイト
Essential前提知識
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 が実行されるタイミング
コンパイラは、値が宣言されたブロックの末尾、またはスコープを早期に抜ける return、break、? のタイミングで 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 は値の所有権を受け取る(関数内にムーブする)ことで、通常のドロップパスを起動し、所有権ルールを満たす。
Drop と Copy は相互排他
Copy 型は使われるたびにビット単位で複製される — コンパイラはどのコピーが「本物」かを追跡せずに自由にコピーを作る。Drop はちょうど一人の所有者がちょうど一回の drop 呼び出しを受け取ることを要求する。この二つの要件は両立しないため、両方を実装しようとする型はコンパイラに拒否される。