問題の歴史:
Rustでは、ガーベジコレクタなしでリソース管理が可能になったのは、オブジェクトの所有権とライフサイクルに関する厳格なルールのおかげです。リソース解放の自動化(たとえば、ファイルディスクリプタ、ソケット、外部ライブラリからのメモリ)を実現するために、言語の初期段階からDropトレイトが導入されました。これは、オブジェクトのライフサイクルの終了に対する基本的な「反応」であり、リソースをオペレーティングシステムに返却するためやメモリを解放するために使用されます。
問題:
Rustの通常の型は自動的に自分のリソースをクリーンアップしますが、構造体が手動で解放する必要のあるリソース(たとえば、オープンファイルやunsafeポインタ)を保持している場合、開発者の不注意や忘却がリソースのリークやレースコンディションを引き起こす可能性があります。Dropが正しく実装されていない場合(たとえば、メモリの二重解放が考慮されていない場合)、ランタイムエラーを引き起こす可能性があります。
解決策:
Dropトレイトは、値が破棄されるときに自動的に呼び出される特別なメソッドdrop(&mut self)を定義することを可能にします。これは、オブジェクトがスコープを超えたときにリソースを解放するのに適しています。リソースを解放する(たとえば、ファイルを閉じる)必要があるのは、ここだけであることを覚えておくことが重要です。
コード例:
struct RawFile { handle: *mut libc::FILE, } impl Drop for RawFile { fn drop(&mut self) { if !self.handle.is_null() { unsafe { libc::fclose(self.handle); } } } }
主な特徴:
オブジェクトのリソースを早めに解放するためにdropを明示的に呼び出すことはできますか?
いいえ、メソッドdrop(&obj.drop())を直接呼び出すことは禁止されています — これはstd::mem::drop(obj)関数を通じてのみ呼び出すことができ、所有権を持つオブジェクトを引数に取って自動的にdropを呼び出します。drop()を直接呼び出すことはコンパイルエラーになります。
コード例:
fn main() { let f = File::open("foo.txt").unwrap(); // drop(&mut f); // コンパイルエラー! std::mem::drop(f); // 正しい:ファイルを閉じる }
Dropを持つ構造体がmemcpyまたはmoveに入るとどうなりますか?デストラクタが二重に呼び出されることはありませんか?
いいえ、デストラクタはオブジェクトのライフサイクル全体で正確に1回呼び出され、コンパイラがこれを監視します。しかし、構造体の"生"バイトをunsafeにコピーするか、mem::forgetを使用した場合、dropが全く呼ばれない可能性があります — これが危険性です。
同じ型に対してDropとCopyの両方を同時に実装できますか?
いいえ、コンパイラはこの組み合わせを禁止します:Dropを実装する型はCopyではなく、一度だけデストラクタを呼び出し、二重解放を防ぐことが保証される必要があります。
プログラマはオープンファイルを扱うためのシンプルなラッパーを実装しましたが、Dropを実装しておらず、オブジェクトが削除されたときにディスクリプタを閉じるのを忘れました。
利点:
欠点:
開発者はファイルディスクリプタのラッパーにDropを実装し、drop内でファイルを明示的に閉じました。これにより、この構造体の任意の変数が関数を出たりpanicした場合に、リソースが確実に解放されます。
利点:
欠点: