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

スマートポインタパターンにとって重要な2番目のトレイトは、Dropであり、 これのおかげで値がスコープを抜けそうになった時に起こることをカスタマイズできます。 どんな型に対してもDropトレイトの実装を提供することができ、指定したコードは、 ファイルやネットワーク接続などのリソースを解放するのに活用できます。 Dropをスマートポインタの文脈で導入しています。Dropトレイトの機能は、ほぼ常にスマートポインタを実装する時に使われるからです。 例えば、Box<T>Dropをカスタマイズしてボックスが指しているヒープの領域を解放しています。

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

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

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

ファイル名: 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") };   // 別のもの
    println!("CustomSmartPointers created.");                           // CustomSmartPointerが生成された
}

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

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

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

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

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

fn main() {
    let c = CustomSmartPointer { data: String::from("some data") };
    println!("CustomSmartPointer created.");
    c.drop();
    // mainの終端の前にCustomSmartPointerがドロップされた
    println!("CustomSmartPointer dropped before the end of main.");
}

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

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

error[E0040]: explicit use of destructor method
(エラー: デストラクタメソッドを明示的に使用しています)
  --> src/main.rs:14:7
   |
14 |     c.drop();
   |       ^^^^ explicit destructor calls not allowed

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

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

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

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

ファイル名: src/main.rs

# struct CustomSmartPointer {
#     data: String,
# }
#
# impl Drop for CustomSmartPointer {
#     fn drop(&mut self) {
#         println!("Dropping CustomSmartPointer!");
#     }
# }
#
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を呼び出す

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

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