Chapter 14

RAII

📌 RAIIとは

RAII (Resource Acquisition Is Initialization) とはリソースの確保をオブジェクトの初期化時に行い,リソースの開放をオブジェクトの破棄と同時に行う手法です.これから解説する内容は公式だとスマートポインタと呼ばれていますが,安易にポインタという用語を使うべきでないと思っているし,RAII はポインタに限った話でもないので本書では使いません.

📌 Deref トレイト

実は,Rust の 参照 は RAII の最も代表となる1つです.参照は仮の所有権を保持し,スコープから外れると仮の所有権を破棄します.参照の機能は大きく2つあります.参照先のオブジェクトを操作できること,参照先のオブジェクトの仮の所有権を破棄することです.このうち,参照先のオブジェクトを操作するには 参照外し が必要です.これを実現しているのが Deref トレイトです.参照外しは参照に対して * 演算子を使います.このように,RAII におけるリソースに対して操作をするには Deref トレイトを実装し * 演算子を使うことです.また,可変参照に対しては DerefMut トレイトを実装します.

📌 Drop トレイト

参照のもう1つの機能が仮の所有権の破棄です.これは Drop トレイトで実装します. Drop トレイトのインスタンスのオブジェクトは,それが破棄されるときに drop メソッドが呼ばれます.このメソッドでリソースの開放処理を行います. drop メソッドは可変参照を引数に取ります.

impl Drop for Resource {
    fn drop(&mut self) {
        ...
    }
}

drop メソッドは基本的にオブジェクトが破棄されるときに自動で呼び出されますが,明示的に呼びたい場合があるかもしれません.その場合は, std::mem::drop 関数で強制的に呼び出してオブジェクトを破棄することが出来ます.ただし,あまり使うべきではありません.

📌 メモリ

Rust では次の3つのメモリ領域があります.1つ目は データメモリ で,静的データが格納されています.静的データはプログラム実行中に存在するデータのことです.2つ目は スタックメモリ です.これは変数や関数呼び出し時の引数など一時的な格納場所で,コンパイラが最適化しやすく高速にデータ操作が出来ます.3つ目は ヒープメモリ です.ここにはプログラム実行中に利用できるメモリで,スタックメモリよりも大きなサイズを利用することが出来ます.ただし,利用するにはオーバーヘッドがかかります.また,スタックメモリのサイズはヒープメモリに比べてかなり限られているので,ヒープメモリを積極的に利用することになります.

📌 Box

これまで,メモリを意識してきませんでした.基本的に作成したオブジェクトはスタックメモリに置かれます.また,static に指定したオブジェクトや文字列リテラルなどはデータメモリに置かれます.では,ヒープメモリに置くにはどうすればよいでしょうか.それが Box<T> です.使い方は簡単で, Box::new または Box::<T>::new にオブジェクトを渡すだけです.

let a = Box::new(10);        // type inference
let a = Box::<i32>::new(20); // explicit type
let a = 30;                  // immutable object
let b = Box::new(a);         // move object from stack memory to heap memory
let c = *b;                  // dereference

この図,覚えてますか?

rust-onwership01

Box を表すと…

rust-box

📌 Rc

Rc (Reference Count) とは 参照カウンタ のことです.原本の所有権を束縛できるのは1つだけです.参照を使えば,仮の所有権を作成することができます.しかし,参照は制約が強いです.同じオブジェクトを複数から束縛することは出来ないのでしょうか.それを可能にするのが参照カウンタで, Rc<T> です. Rc は原本または仮の所有権を保持することが出来ます.基本的な機能としては参照と変わらず,仮の所有権を作成します.ただし, & 演算子ではなく clone というメソッドです.

use std::rc::Rc;

let a = Rc::new(10);
let b = a.clone();

この clone メソッドはディープコピーの clone と勘違いしやすいため,関連関数の clone を使うほうが良いらしいです.

use std::rc::Rc;

let a = Rc::new(10);
let b = Rc::clone(&a);

通常は,原本の所有権を束縛した変数が破棄されるときは,仮の所有権を持った変数は存在してはいけません.しかし, Rc の場合は,原本の所有権を束縛した変数が破棄されたときに, clone で作成した仮の所有権を束縛した変数が存在することができます.このとき,オブジェクトは破棄されず,すべての仮の所有権を束縛した変数が破棄されたときにオブジェクトが破棄されます.

また,この図が出てきました.

rust-onwership01

Rc を表すと…

rust-rc01

Rc はオブジェクトをヒープメモリに置きます.なので, Box の代わりに使うことができます.

use std::rc::Rc;

fn main() {
    let a = Rc::new(10);
    let b = Rc::clone(&a);
    println!("{} {}", a, b);
}