Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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 を呼び出し、私たちが 指定したコードを実行しました。変数は作成された順序の逆順でドロップされるため、 dc より先にドロップされました。この例の目的は、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 createdCustomSmartPointer dropped before the end of main のテキストの間に表示されます。これにより、その時点で c をドロップするために drop メソッドのコードが呼び出されていることがわかります。

Drop トレイト実装で指定したコードは、クリーンアップを便利かつ安全にするために さまざまな方法で利用できます。たとえば、独自のメモリアロケータを作るためにも 利用できます。Drop トレイトと Rust の所有権システムにより、Rust が自動的に クリーンアップしてくれるので、自分でそれを覚えておく必要はありません。

また、まだ使われている値を誤ってクリーンアップしてしまうことで生じる問題についても 心配する必要はありません。参照が常に有効であることを保証する所有権システムは、 値がもう使われなくなったときに drop が 1 回だけ呼ばれることも保証します。

ここまでで Box<T> とスマートポインタのいくつかの特徴を見てきたので、次は標準 ライブラリで定義されている別のスマートポインタをいくつか見ていきましょう。