項目11:RAIIパターンにはDropトレイトを実装しよう
https://effective-rust.com/raii.html
RAII はメモリ以外のリソース管理にも有用
Rust では 所有権 を用いたメモリ管理を用いているが、RAII パターン は他のリソースに対しても有用
「解放しなければならない何らかのリソースを保持する型には、Drop を実装しよう」
e.g. 使用例
OS のリソースにアクセスするケース
Unix 由来のシステムでは、多くの場合 ファイルディスクリプタ を保持することになる
これを正しく解放しないと、システムのリソースを保持したままになり、最終的にはプロセスごとのファイルディスクリプタの上限に達する
同期用(並行処理 や マルチスレッド 環境でのリソースの共有やアクセス制御)リソースにアクセスするケース
メモリ同期機構については標準ライブラリに用意されている
e.g. Mutex / RwLock
しかし、他のリソース(ファイルロックやデータベースロック)に対しても同様の カプセル化 が必要な場合がある
生メモリにアクセスするケース
低レイヤのメモリ管理を行う unsafe な型(e.g. FFI など)
標準ライブラリにおける RAII パターン の例
Mutex::lock() で返される MutexGuard
Mutex のロックを保持している間は、MutexGuard を介して保護されたデータにアクセスする
code:rs
use std::sync::Mutex;
struct ThreadSafeInt {
value: Mutex<i32>,
}
impl ThreadSafeInt {
fn new(value: i32) -> Self {
Self { value: Mutex::new(value) }
}
fn add(&self, delta: i32) {
let mut v = self.value.lock().unwrap();
*v += delta;
}
}
ロックは長く持たないようにしたい(項目17:状態共有並列実行には気を付けよう)ため、「ブロックを使って RAII のスコープを制限する」
code:rs
impl TreadSafeInt {
fn add_with_extras(&self, delta: i32) {
{
let mut v = self.value.lock().unwrap();
*v += delta;
}
// ロックを必要としないコードが続く...
}
}
RAII パターンの実装方法
Drop トレイトを用いると、値の デストラクション 時の挙動をユーザ定義可能
必須メソッドは drop のみ
コンパイラ はメモリを解放する直前に、このメソッドを呼び出す
warning.icon
このメソッドは明示的に呼び出すことができない
code:rs
x.drop();
理由
シグネチャは drop(&mut self) であり、移動した値を受け取るのではなく可変参照を受け取る
そのため、drop メソッドが明示的に呼び出せると、内部状態やリソースが解放されているにも関わらず、呼び出した後でも使用できてしまう
code:rs
{
x.drop();
x.0 += 1;
}
回避策: drop 関数 を呼び出す
この関数では、引数をムーブで受け取るようになっている
pub fn drop<T>(_x: T)
また、実装は空のボディである
code:rs
pub fn drop<T>(_x: T) {}
したがって、ムーブした値はこの関数の波括弧が閉じたタイミングでドロップされる
戻り値の型は無い
そのため、リソースの解放に失敗する可能性がある 場合、Result 型を返す release メソッドを作成し、失敗を検出できるようにする必要がある
実装例
code:rs
#derive(Debug)
struct MyStruct(i32);
impl Drop for MyStruct {
fn drop(&mut self) {
println!("Dropping {self:?}");
}
}
#Rust #Effective_Rust_―_Rustコードを改善し、エコシステムを最大限に活用するための35項目