TL;DR
- RAII パターンは、メモリ以外のリソースの管理にも役立つ。
- 解放しなければならないリソースを持つ型には、
Dropトレイトを実装する。 dropメソッドは直接呼べず、戻り値もない。解放に失敗しうるなら、Resultを返すreleaseメソッドを別に用意する。
内容
https://effective-rust.com/raii.html
メモリ以外のリソース
Rust は所有権によってメモリを管理するが、RAII パターンは他のリソースにも有効である。
解放しなければならない何らかのリソースを保持する型には、Drop を実装しよう
たとえば次のようなリソースがある。
- OS のリソース: Unix 系のシステムでは、多くの場合ファイルディスクリプタを持つ。正しく解放しないとリソースを持ち続け、最後にはプロセスごとのファイルディスクリプタの上限に達する
- 同期のためのリソース: メモリの同期には標準ライブラリの
MutexやRwLockがあるが、ファイルロックやデータベースのロックにも同じようなカプセル化が必要になることがある - 生のメモリ: FFI などで低レベルのメモリ管理を行う
unsafeな型
標準ライブラリの例
Mutex::lock() が返す MutexGuard は RAII パターンの例である。ロックを持っている間は、MutexGuard を通して保護されたデータにアクセスする。MutexGuard がドロップされると、ロックが解放される。
ロックは長く持たないほうがよい(項目17)。そこで、ブロックを使って RAII のスコープを狭める。
impl ThreadSafeInt {
fn add_with_extras(&self, delta: i32) {
{
let mut v = self.value.lock().unwrap();
*v += delta;
}
// ロックを必要としないコードが続く
}
}Drop トレイトの実装
Drop トレイトを実装すると、値が破棄されるときの振る舞いを定義できる。必須のメソッドは drop だけで、コンパイラはメモリを解放する直前にこれを呼ぶ。
#[derive(Debug)]
struct MyStruct(i32);
impl Drop for MyStruct {
fn drop(&mut self) {
println!("Dropping {self:?}");
}
}drop メソッドには 2 つの注意点がある。
1 つ目は、x.drop() のように明示的に呼べないことである。シグネチャは drop(&mut self) で、値の所有権ではなく可変参照を受け取る。明示的に呼べると、リソースを解放したあとの値を使えてしまう。早めに破棄したいときは、std::mem::drop 関数を使う。この関数は引数をムーブで受け取り、中身は空である。
pub fn drop<T>(_x: T) {}ムーブされた値は、この関数の終わりで破棄される。
2 つ目は、戻り値がないことである。リソースの解放に失敗しうる場合は、Result を返す release メソッドを作り、失敗を検出できるようにする必要がある。