📌 RAIIとは

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

📌 Deref トレイト

Rust の 参照 は RAII と考えられます.参照は仮の所有権を保持し,スコープから外れると仮の所有権を破棄します.参照の機能は大きく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-ownership01

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-ownership01

Rc型を表すと次のようになります.

rust-rc01

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