ガードを用いた RAII
説明
RAII は “Resource Acquisition Is Initialization” の略で、率直に言えばあまり分かりやすい名前ではありません。このパターンの本質は、リソースの取得や初期化をオブジェクトのコンストラクタで行い、解放や後始末をデストラクタで行うことです。このパターンは Rust では、RAII オブジェクトを何らかのリソースのガードとして使い、アクセスが常にガードオブジェクトによって仲介されることを型システムに保証させることで拡張されています。
例
ミューテックスガードは、標準ライブラリにおけるこのパターンの古典的な例です(これは実際の実装を簡略化したものです)。
use std::ops::Deref;
struct Foo {}
struct Mutex<T> {
// ここでは、データ T への参照を保持します。
//..
}
struct MutexGuard<'a, T: 'a> {
data: &'a T,
//..
}
// ミューテックスのロックは明示的です。
impl<T> Mutex<T> {
fn lock(&self) -> MutexGuard<T> {
// 内部の OS ミューテックスをロックします。
//..
// MutexGuard は self への参照を保持します
MutexGuard {
data: self,
//..
}
}
}
// ミューテックスのロックを解除するためのデストラクターです。
impl<'a, T> Drop for MutexGuard<'a, T> {
fn drop(&mut self) {
// 下層の OS ミューテックスのロックを解除します。
//..
}
}
// Deref を実装すると、MutexGuard を T へのポインターのように扱えます。
impl<'a, T> Deref for MutexGuard<'a, T> {
type Target = T;
fn deref(&self) -> &T {
self.data
}
}
fn baz(x: Mutex<Foo>) {
let xx = x.lock();
xx.foo(); // foo は Foo のメソッドです。
// 借用チェッカーは、ガード xx より長く生存する下層の
// Foo への参照を保存できないことを保証します。
// この関数を抜けると `xx` のデストラクタが実行され、`x` のロックが解除されます。
}
動機
リソースを使用後に終了処理しなければならない場合、RAII を使ってこの終了処理を行えます。終了処理後にそのリソースへアクセスすることがエラーである場合、このパターンを使ってそのようなエラーを防げます。
利点
リソースが終了処理されないエラーや、リソースが終了処理後に使用されるエラーを防ぎます。
考察
RAII は、リソースが適切に解放または終了処理されることを保証するための有用なパターンです。Rust の借用チェッカーを活用することで、終了処理が行われた後にリソースを使用することに起因するエラーを静的に防げます。
借用チェッカーの中心的な目的は、データへの参照がそのデータより長く生存しないようにすることです。RAII ガードパターンが機能するのは、ガードオブジェクトが下層のリソースへの参照を含み、そのような参照のみを公開するためです。Rust は、ガードが下層のリソースより長く生存できないこと、およびガードによって仲介されるリソースへの参照がガードより長く生存できないことを保証します。これがどのように機能するかを理解するには、ライフタイム省略なしの deref のシグネチャを調べると役立ちます。
fn deref<'a>(&'a self) -> &'a T {
//..
}
返されるリソースへの参照は、self ('a) と同じライフタイムを持ちます。したがって、借用チェッカーは、T への参照のライフタイムが self の借用のライフタイムを超えないことを保証します。
Deref の実装はこのパターンの中核ではなく、ガードオブジェクトをより扱いやすくするだけであることに注意してください。ガードに get メソッドを実装しても同様に機能します。
関連項目
RAII は C++ で一般的なパターンです: cppreference.com, wikipedia。
スタイルガイドの項目 (現在は単なるプレースホルダーです)。