Dropトレイトで片付け時にコードを走らせる

スマートポインタパターンにとって重要な2番目のトレイトは、Dropであり、 これのおかげで値がスコープを抜けそうになった時に起こることをカスタマイズできます。 どんな型に対してもDropトレイトの実装を提供することができ、 そのコードはファイルやネットワーク接続などのリソースを解放するために使用できます。

Dropをスマートポインタの文脈で導入しています。Dropトレイトの機能は、ほぼ常にスマートポインタを実装する時に使われるからです。 例えばBox<T>はドロップされるときに、そのボックスが指しているヒープの領域を解放するでしょう。

言語によっては、さらに型によっては、プログラマがその型のインスタンスを使い終わる度に、 メモリやリソースを解放するコードを呼ばなければならないことがあります。 そうした型の例にはファイルハンドル、ソケット、またはロックなどが含まれます。 忘れてしまったら、システムは詰め込みすぎになりクラッシュする可能性があります。Rustでは、 値がスコープを抜ける度に特定のコードが走るよう指定でき、コンパイラはこのコードを自動的に挿入します。 結果として、特定の型のインスタンスを使い終わったプログラムの箇所全部にクリーンアップコードを配置するのに配慮する必要はありません。 それでもリソースをリークすることはありません。

Dropトレイトを実装することで、値がスコープを抜けた時に走るコードを指定します。 Dropトレイトは、selfへの可変参照を取るdropという1つのメソッドを実装する必要があります。 いつRustがdropを呼ぶのか確認するために、今はprintln!文のあるdropを実装しましょう。

リスト15-14では、コンパイラがいつdrop関数を走らせるかを示すために、 インスタンスがスコープを抜ける時にDropping CustomSmartPointer!と出力するだけの独自の機能を持つ、 CustomSmartPointer構造体を示します。

ファイル名: src/main.rs

struct CustomSmartPointer {
    data: String,
}

impl Drop for CustomSmartPointer {
    fn drop(&mut self) {
        //       "データ`{}`を持つCustomSmartPointerをドロップ中!"
        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"),
    };
    //       "CustomSmartPointerが作成された。"
    println!("CustomSmartPointers created.");
}

リスト15-14: クリーンアップコードを配置するDropトレイトを実装するCustomSmartPointer構造体

Dropトレイトは、初期化処理に含まれるので、スコープ内に持ち込む必要はありません。 CustomSmartPointerDropトレイトを実装し、println!を呼び出すdropメソッドの実装を提供しています。 drop関数の本体は、自分の型のインスタンスがスコープを抜ける時に走らせたいあらゆるロジックを配置する場所です。 コンパイラがいつdropを呼ぶのかを視覚的に示すために、ここでテキストを出力しています。

mainで、CustomSmartPointerのインスタンスを2つ作り、それからCustomSmartPointers created.と出力しています。 mainの最後で、CustomSmartPointerのインスタンスはスコープを抜け、コンパイラは最後のメッセージを出力しながら、 dropメソッドに置いたコードを呼び出します。dropメソッドを明示的に呼び出す必要はなかったことに注意してください。

このプログラムを実行すると、以下のような出力が出ます:

$ cargo run
   Compiling drop-example v0.1.0 (file:///projects/drop-example)
    Finished dev [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`!

インスタンスがスコープを抜けた時に指定したコードを呼び出しながらコンパイラは、dropを自動的に呼び出してくれました。 変数は、生成されたのと逆の順序でドロップされるので、dcより先にドロップされました。 この例の目的はdropメソッドがどう機能するのかを視覚的に案内することですが、通常は、メッセージ出力ではなく、 自分の型が走らせる必要のあるクリーンアップコードを指定するでしょう。

std::mem::dropで早期に値をドロップする

残念ながら、自動的なdrop機能を無効化することは、単純ではありません。通常、dropを無効化する必要はありません; Dropトレイトの最重要な要点は、自動的に考慮されることです。ですが、時として、値を早期に片付けたくなる可能性があります。 一例は、ロックを管理するスマートポインタを使用する時です: 同じスコープの他のコードがロックを獲得できるように、 ロックを解放するdropメソッドを強制したくなる可能性があります。Rustは、 Dropトレイトのdropメソッドを手動で呼ばせてくれません; スコープが終わる前に値を強制的にドロップさせたいなら、 代わりに標準ライブラリが提供するstd::mem::drop関数を呼ばなければなりません。

リスト15-14のmain関数を変更して手動でDropトレイトのdropメソッドを呼び出そうとしたら、 コンパイルエラーになるでしょう。リスト15-15のようにですね:

ファイル名: src/main.rs

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();
    //       "CustomSmartPointerがmainの終端の前でドロップされた。"
    println!("CustomSmartPointer dropped before the end of main.");
}

リスト15-15: Dropトレイトからdropメソッドを手動で呼び出し、早期に片付けようとする

このコードをコンパイルしてみようとすると、こんなエラーが出ます:

$ 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
(ヘルプ: `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を呼び出すことは許されていないことをこのエラーメッセージは述べています。 エラーメッセージはデストラクタという専門用語を使っていて、これは、 インスタンスを片付ける関数の一般的なプログラミング専門用語です。デストラクタは、 コンストラクタに類似していて、これはインスタンスを生成します。Rustのdrop関数は、 1種の特定のデストラクタです。

コンパイラはそれでも、mainの終端で値に対して自動的にdropを呼び出すので、dropを明示的に呼ばせてくれません。 コンパイラが2回同じ値を片付けようとするので、これは二重解放エラーを発生させるでしょう。

値がスコープを抜けるときにdropが自動的に挿入されるのを無効化できず、dropメソッドを明示的に呼ぶこともできません。 よって、値を早期に片付けさせる必要があるなら、std::mem::drop関数を使用します。

std::mem::drop関数は、Dropトレイトのdropメソッドとは異なります。 強制ドロップさせたい値を引数として渡して呼び出します。この関数はpreludeに含まれているので、 リスト15-15のmainを変更してdrop関数を呼び出せます。リスト15-16のようにですね:

ファイル名: src/main.rs

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);
    //       "CustomSmartPointerがmainの終端の前でドロップされた。"
    println!("CustomSmartPointer dropped before the end of main.");
}

リスト15-16: 値がスコープを抜ける前に明示的にドロップするためにstd::mem::dropを呼び出す

このコードを実行すると、以下のように出力されます:

$ cargo run
   Compiling drop-example v0.1.0 (file:///projects/drop-example)
    Finished dev [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.テキストの間に出力されるので、 dropメソッドのコードがその時点で呼び出されてcをドロップしたことを示しています。

Dropトレイト実装で指定されたコードをいろんな方法で使用し、片付けを便利で安全にすることができます: 例を挙げれば、これを使用して独自のメモリアロケータを作ることもできるでしょう!DropトレイトとRustの所有権システムがあれば、 コンパイラが自動的に行うので、片付けを覚えておく必要はなくなります。

まだ使用中の値を間違って片付けてしまうことに起因する問題を心配する必要もなくて済みます: 参照が常に有効であると確認してくれる所有権システムが、値が最早使用されなくなった時にdropが1回だけ呼ばれることを保証してくれるのです。

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