(´ワ`)

主に技術系備忘録、たまに日記。

Rustのスマートポインタまとめ

最近まじめにRustを勉強し始めたので主要なスマートポインタについてRustドキュメントの内容を自分用にまとめる。

Box<T>

T の値をヒープに格納する。型の中に同じ型が入るような無限の再帰ループを起こすような型のように、 T のサイズが未確定のときに使える。RustドキュメントにはConsが例として挙げられている。

enum List {
  Cons(i32, List),
  Nil,
}

fn main() {
  use List::{Cons, Nil};
  let ls = Cons(1, Cons(2, Cons(3, Nil)));  // Consの中にConsが入っており再帰的構造になっているので、コンパイラが必要なデータのサイズを計算できない
  println!("{:?}", ls);
}
enum List {
  Cons(i32, Box<List>) // Boxで定義することで再帰的構造を計算できる
  Nil
}

fn main() {
  use List::{Cons, Nil};
  let ls = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil)))));
  println!("{:?}", ls);

Boxはポインタとして間接参照されるのでポインタがデータ構造の代わりに格納される。そのためデータの中身がどのくらいの量であるか関係なくコンパイラが解釈してくれる。

Rc<T>

Referencecounter。
複数の参照を可能にするスマートポインタ。参照されている数をカウントする機能も持っており(string_count(), weak_count)、このカウントが0になったとき自動で破棄されるようになっている。先程のConsを例にすると、以下のようなことができる。

#[derive(Debug)]
enum List {
    Cons(i32, Box<List>),
    Nil,
}

fn main() {
    use List::{Cons, Nil};
    let ls = Box::new(Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil)))))));
    println!("{:?}", ls);

    let ls2 = Box::new(Cons(3, ls));  // この時点で所有権がls2へ移る
    println!("{:?}", ls2);

    let ls3 = Box::new(Cons(4, ls));  // ls2へ参照権が移っているのでエラー
    println!("{:?}", ls3);
}
use std::rc::Rc;

#[derive(Debug)]
enum List {
    Cons(i32, Rc<List>),
    Nil,
}

fn main() {
    use List::{Cons, Nil};
    let ls = Rc::new(Cons(1, Rc::new(Cons(2, Rc::new(Cons(3, Rc::new(Nil)))))));  // Rc::newで作成( カウント: 1
    println!("{:?}", ls);
    println!("{}", Rc::strong_count(&ls));

    let ls2 = Rc::new(Cons(3, Rc::clone(&ls)));  // 変数lsをクローンして複製 (カウント: 2
    println!("{:?}", ls2);
    println!("{}", Rc::strong_count(&ls));  // この時点でカウント2

    let ls3 = Rc::new(Cons(4, Rc::clone(&ls))); // 変数lsをクローンして複製 (カウント: 3
    println!("{:?}", ls3);
    println!("{}", Rc::strong_count(&ls));  // この時点でカウント3
}  // ここでブロックが終わるのでカウント0、破棄

/*
出力
Cons(1, Cons(2, Cons(3, Nil)))
1
Cons(3, Cons(1, Cons(2, Cons(3, Nil))))
2
Cons(4, Cons(1, Cons(2, Cons(3, Nil))))
3
*/

Boxはポインタだけど複数値を参照する機能は持っていないため、所有権が移った時点でRustの所有権の基本に沿ったシンプルなヒープとしての使い方が出来た。Rcを使用すれば複数参照できるヒープとして実装できる。

RefCell<T>

非常に理解に時間がかかった。 RefCellは T の値に対して実行時に可変参照を(強制的に)許可する機能を持つ。そのため、T が可変性を許可していなくてもRefCellの機能で可変参照を強制的に許可できる。テストなどで可変性のある値の中身を自由に入れ替えたいときなどに有効。

// 実装
trait List {
    fn append(&self, val: i32);
}

// ----------------------------------- TEST -----------------------------------

#[derive(Debug)]
struct MockList {
    values: Vec<i32>,
}

impl List for MockList {
    fn append(&self, val: i32) {
        self.values.push(val);  // ここが不変で借用されているのでエラー
    }
}

impl MockList {
    fn new() -> MockList {
        MockList { values: vec![] }
    }
}

// ----------------------------------------------------------------------------

fn main() {
    let ls = MockList::new();
    ls.append(1);
    println!("{:?}", ls);
}
// 実装
trait List {
    fn append(&self, val: i32);
}

// ----------------------------------- TEST -----------------------------------

#[derive(Debug)]
struct MockList {
    values: RefCell<Vec<i32>>,  // RefCell
}

impl List for MockList {
    fn append(&self, val: i32) {
        self.values.borrow_mut().push(val);  // borrow_mut()で強制的に可変借用できる
    }
}

impl MockList {
    fn new() -> MockList {
        MockList {
            values: RefCell::new(vec![]),
        }
    }
}

// ----------------------------------------------------------------------------

fn main() {
    let ls = MockList::new();  // 作った時点では不変
    ls.append(1);  // appendできる
    println!("{:?}", ls);
}

trait List が実際の実装なのでここをテストのために可変参照を許可するようにしたくない。こうしたときに強制的に可変参照を許可できるRefCellを使うことで自由に中身を入れ替えるモックを作ることができる。

Mutex<T>・Arc<T>

Mutexはスレッドセーフな可変参照を可能にする。また、Arc<T>はMutex同様スレッドセーフに複数参照を可能にする。すなわち、MutexとArcはそれぞれRefCellとRcのスレッドセーフ版という感じ。
また、Mutexはlock()メソッドが実装されている。これを呼ぶことで排他制御を保証するためにロックの有無をLockResultとして返す。

スマートポインタはまだ種類があるみたいだけどひとまず以上。

つぎはtraitの動的ディスパッチャと静的ディスパッチャについてまとめておきたい(ちょっと勉強したけどやっぱ忘れた)