注意: いい加減な記事です。ドキュメントで確認したわけではなくて書いていて体験したこと書いています。正式な情報を持っていれば教えてもらえるとありがたいです。
バージョン
rustc 1.43.0 (4fb7144ed 2020-04-20)
何
RustのDrop
トレイトはC++でいうところのデストラクタを実装するためのトレイトでRAIIによる資源の管理に利用できる。RAIIとはそのクラスが(主にコンストラクタで確保して)所有するリソースを、デストラクタで解放することによってリソース管理を自動化するテクニックだ。例えばコンストラクタでヒープメモリをalloc
/new
してデストラクタでfree
/delete
するとか、コンストラクタでmutexのロックを取得してデストラクタでロックを解放するとかいうふうに利用されている。12
しかし、RustのDrop::drop()
は実装する値の所属するスレッドがデタッチされ、メインスレッドがそのスレッドより早く終了した場合は実行されないらしい。つまり、std::thread::spawn()
の返り値であるstd::thread::JoinHandle
が(std::thread::JoinHandle::join()
に消費されずに)ドロップするとそれに対応するスレッドがデタッチされ、そしてメインスレッドが終了した時点でデタッチされているスレッドは処理を中断されるのだが、3そのときそれらのスレッドに属する値のDrop::drop()
は実行してもらえない。
こうなると、RAIIにおいては資源が解放されないことになる。といっても、メモリのようなOSがプロセスと対応させて管理しているような資源であればどのみちプログラムが終了した時点ですべて解放されることになるから問題にならない。しかし、Drop::drop()
でより外的影響のある操作、例えばファイルのクリーンアップなどを実行しようとしている場合はそれが実行されないことが問題になる可能性は十分あるだろう。
例
よく起こりやすいのは以下のようにバックグラウンドで永続的な処理を行う状況だろう。
use std::thread;
use std::time::Duration;
struct SayBye(&'static str);
impl Drop for SayBye {
fn drop(&mut self) {
println!("bye. {}", self.0);
}
}
fn main() {
let _1 = SayBye("1");
thread::spawn(|| {
// Prepare a Drop-able thing for a background task
let _2 = SayBye("2");
loop {
// Do background task in another thread
}
});
// Do the main task
thread::sleep(Duration::from_secs(1));
}
出力:
bye. 1