Drop トレイトでクリーンアップ時にコードを実行する
スマートポインタパターンで重要な 2 つ目のトレイトは Drop です。これにより、
値がスコープを抜けようとするときに何が起こるかをカスタマイズできます。任意の型に
対して Drop トレイトの実装を提供でき、そのコードはファイルやネットワーク接続などの
リソースを解放するために使えます。
ここで Drop をスマートポインタの文脈で導入するのは、Drop トレイトの機能が
スマートポインタを実装するときにほとんど常に使われるからです。たとえば、Box<T> が
ドロップされると、box が指しているヒープ上の領域が解放されます。
一部の言語では、一部の型に対して、その型のインスタンスの使用を終えるたびに、 メモリやリソースを解放するコードをプログラマが呼び出さなければなりません。例としては、 ファイルハンドル、ソケット、ロックなどがあります。プログラマが忘れると、システムに 過剰な負荷がかかってクラッシュする可能性があります。Rust では、値がスコープを抜ける たびに特定のコードを実行するよう指定でき、コンパイラがこのコードを自動的に挿入します。 その結果、特定の型のインスタンスの使用が終わるたびに、プログラムのあらゆる場所へ クリーンアップコードを配置することに気を配る必要はありません。それでもリソースリークは 起きません。
値がスコープを抜けるときに実行するコードは、Drop トレイトを実装することで指定します。
Drop トレイトでは、self への可変参照を受け取る drop という名前のメソッドを
1 つ実装する必要があります。Rust がいつ drop を呼ぶのかを見るために、いまは
println! 文を使って drop を実装してみましょう。
リスト 15-14 は CustomSmartPointer 構造体を示しています。この構造体の唯一の
カスタム機能は、インスタンスがスコープを抜けるときに Dropping CustomSmartPointer! を
表示することです。これにより、Rust がいつ drop メソッドを実行するのかを示しています。
struct CustomSmartPointer {
data: String,
}
impl Drop for CustomSmartPointer {
fn drop(&mut self) {
println!("Dropping CustomSmartPointer with data `{}`!", self.data);
}
}
fn main() {
let c = CustomSmartPointer {
data: String::from("my stuff"),
};
let d = CustomSmartPointer {
data: String::from("other stuff"),
};
println!("CustomSmartPointers created");
}
Drop トレイトは prelude に含まれているので、スコープに持ち込む必要はありません。
CustomSmartPointer に対して Drop トレイトを実装し、println! を呼び出す
drop メソッドの実装を提供しています。drop メソッドの本体には、自分の型の
インスタンスがスコープを抜けるときに実行したい任意のロジックを配置します。ここでは、
Rust がいつ drop を呼ぶのかを視覚的に示すために、いくつかのテキストを出力しています。
main では、CustomSmartPointer のインスタンスを 2 つ作成し、その後
CustomSmartPointers created を表示します。main の終わりで、
CustomSmartPointer のインスタンスはスコープを抜け、Rust は drop メソッドに
書いたコードを呼び出して、最後のメッセージを表示します。drop メソッドを明示的に
呼び出す必要がなかったことに注目してください。
このプログラムを実行すると、次のような出力が表示されます。
$ cargo run
Compiling drop-example v0.1.0 (file:///projects/drop-example)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.60s
Running `target/debug/drop-example`
CustomSmartPointers created
Dropping CustomSmartPointer with data `other stuff`!
Dropping CustomSmartPointer with data `my stuff`!
インスタンスがスコープを抜けたとき、Rust は自動的に drop を呼び出し、私たちが
指定したコードを実行しました。変数は作成された順序の逆順でドロップされるため、
d は c より先にドロップされました。この例の目的は、drop メソッドがどのように
動作するかを視覚的に示すことです。通常は、出力メッセージではなく、自分の型で
実行する必要があるクリーンアップコードを指定することになるでしょう。
残念ながら、自動的な drop の機能を無効にするのは簡単ではありません。通常、
drop を無効にする必要はありません。Drop トレイトの要点は、それが自動的に
処理されることにあるからです。ただし、値を早めにクリーンアップしたい場合もあります。
一例として、ロックを管理するスマートポインタを使っているときが挙げられます。同じ
スコープ内のほかのコードがロックを取得できるように、ロックを解放する drop
メソッドを強制的に実行したいことがあるかもしれません。Rust では Drop トレイトの
drop メソッドを手動で呼び出すことはできません。その代わり、スコープの終わりより前に
値を強制的にドロップしたい場合は、標準ライブラリが提供する std::mem::drop
関数を呼び出す必要があります。
リスト 15-14 の main 関数を変更して Drop トレイトの drop メソッドを手動で
呼び出そうとしても、リスト 15-15 に示すようにうまくいきません。
struct CustomSmartPointer {
data: String,
}
impl Drop for CustomSmartPointer {
fn drop(&mut self) {
println!("Dropping CustomSmartPointer with data `{}`!", self.data);
}
}
fn main() {
let c = CustomSmartPointer {
data: String::from("some data"),
};
println!("CustomSmartPointer created");
c.drop();
println!("CustomSmartPointer dropped before the end of main");
}
このコードをコンパイルしようとすると、次のエラーが表示されます。
$ cargo run
Compiling drop-example v0.1.0 (file:///projects/drop-example)
error[E0040]: explicit use of destructor method
--> src/main.rs:16:7
|
16 | c.drop();
| ^^^^ explicit destructor calls not allowed
|
help: consider using `drop` function
|
16 - c.drop();
16 + drop(c);
|
For more information about this error, try `rustc --explain E0040`.
error: could not compile `drop-example` (bin "drop-example") due to 1 previous error
このエラーメッセージは、drop を明示的に呼び出すことは許されていないと述べています。
このエラーメッセージでは、destructor という用語が使われています。これは、
インスタンスをクリーンアップする関数を指す一般的なプログラミング用語です。
destructor は、インスタンスを生成する constructor に対応するものです。
Rust の drop 関数は、ある特定のデストラクタです。
Rust が drop を明示的に呼び出せないようにしているのは、Rust が main の終わりでも
その値に対して自動的に drop を呼ぶからです。そうすると、Rust が同じ値を 2 回
クリーンアップしようとするため、二重解放エラーが発生します。
値がスコープを抜けるときに drop が自動挿入される仕組みは無効にできず、drop
メソッドを明示的に呼び出すこともできません。したがって、値を早めにクリーンアップする
必要がある場合は、std::mem::drop 関数を使います。
std::mem::drop 関数は、Drop トレイトの drop メソッドとは異なります。
強制的にドロップしたい値を引数として渡して呼び出します。この関数は prelude に
含まれているので、リスト 15-15 の main を変更して drop 関数を呼び出すことが
できます。これはリスト 15-16 に示されています。
struct CustomSmartPointer {
data: String,
}
impl Drop for CustomSmartPointer {
fn drop(&mut self) {
println!("Dropping CustomSmartPointer with data `{}`!", self.data);
}
}
fn main() {
let c = CustomSmartPointer {
data: String::from("some data"),
};
println!("CustomSmartPointer created");
drop(c);
println!("CustomSmartPointer dropped before the end of main");
}
このコードを実行すると、次のように表示されます。
$ cargo run
Compiling drop-example v0.1.0 (file:///projects/drop-example)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.73s
Running `target/debug/drop-example`
CustomSmartPointer created
Dropping CustomSmartPointer with data `some data`!
CustomSmartPointer dropped before the end of main
Dropping CustomSmartPointer with data `some data`! というテキストが、
CustomSmartPointer created と CustomSmartPointer dropped before the end of main
のテキストの間に表示されます。これにより、その時点で c をドロップするために
drop メソッドのコードが呼び出されていることがわかります。
Drop トレイト実装で指定したコードは、クリーンアップを便利かつ安全にするために
さまざまな方法で利用できます。たとえば、独自のメモリアロケータを作るためにも
利用できます。Drop トレイトと Rust の所有権システムにより、Rust が自動的に
クリーンアップしてくれるので、自分でそれを覚えておく必要はありません。
また、まだ使われている値を誤ってクリーンアップしてしまうことで生じる問題についても
心配する必要はありません。参照が常に有効であることを保証する所有権システムは、
値がもう使われなくなったときに drop が 1 回だけ呼ばれることも保証します。
ここまでで Box<T> とスマートポインタのいくつかの特徴を見てきたので、次は標準
ライブラリで定義されている別のスマートポインタをいくつか見ていきましょう。