やってみる

アウトプットすべく己を導くためのブログ。その試行錯誤すらたれ流す。

Rustのスマートポインタ(Weak<T>)

 弱参照。強参照の参照カウンタがゼロになれば弱参照は循環していようが削除される。

成果物

参考

循環参照

 所有者が複数あるRc<T>で循環参照したとき、参照カウンタがゼロになりメモリ解放されない。デッドロックされた状態とりなりメモリリークする。そのままメモリを食いつぶしてシステムがフリーズする。

コード例

main.rs

use std::rc::Rc;
use std::cell::RefCell;
use List::{Cons, Nil};
#[derive(Debug)]
enum List {
    Cons(i32, RefCell<Rc<List>>),
    Nil,
}
impl List {
    fn tail(&self) -> Option<&RefCell<Rc<List>>> {
        match *self {
            Cons(_, ref item) => Some(item),
            Nil => None,
        }
    }
}

fn main() {
    let a = Rc::new(Cons(5, RefCell::new(Rc::new(Nil))));
    println!("a initial rc count = {}", Rc::strong_count(&a));
    println!("a next item = {:?}", a.tail());

    let b = Rc::new(Cons(10, RefCell::new(Rc::clone(&a))));
    println!("a rc count after b creation = {}", Rc::strong_count(&a));
    println!("b initial rc count = {}", Rc::strong_count(&b));
    println!("b next item = {:?}", b.tail());

    if let Some(link) = a.tail() {
        *link.borrow_mut() = Rc::clone(&b);
    }

    println!("b rc count after changing a = {}", Rc::strong_count(&b));
    println!("a rc count after changing a = {}", Rc::strong_count(&a));

    // 次の行のコメントを外して循環していると確認してください; スタックオーバーフローします
    println!("a next item = {:?}", a.tail());        // aの次の要素 = {:?}
}

 最後のprintln!にあるa.tail()を実行させる。

スタックがオーバーフローするまでコンパイラはこの循環を出力しようとする

循環参照を作る直後にプログラムは終了します。

 循環参照をヒープに作るも、その前にスタック領域が食いつぶされてオーバーフローする。ということだと思う。

$ rustc main.rs
$ ./main
...
thread 'main' has overflowed its stack
fatal runtime error: stack overflow
中止

 最後のprintln!コメントアウトすれば以下。

$ rustc main.rs
$ ./main
a initial rc count = 1
a next item = Some(RefCell { value: Nil })
a rc count after b creation = 2
b initial rc count = 1
b next item = Some(RefCell { value: Cons(5, RefCell { value: Nil }) })
b rc count after changing a = 2
a rc count after changing a = 2

循環参照を回避する(Rc<T>Weak<T>

木構造

use std::rc::Rc;
use std::cell::RefCell;
#[derive(Debug)]
struct Node {
    value: i32,
    children: RefCell<Vec<Rc<Node>>>,
}
fn main() {
    let leaf = Rc::new(Node {
        value: 3,
        children: RefCell::new(vec![]),
    });
    let branch = Rc::new(Node {
        value: 5,
        children: RefCell::new(vec![Rc::clone(&leaf)]),
    });
}

 親は子を所有する。

親を参照する

use std::rc::{Rc, Weak};
use std::cell::RefCell;
#[derive(Debug)]
struct Node {
    value: i32,
    parent: RefCell<Weak<Node>>,
    children: RefCell<Vec<Rc<Node>>>,
}

 parentの型にWeak<T>を使う。親子ともRc<T>ならbranchleafで循環参照になってしまう。

 親は子を所有すべき。親が死ねば子も死ぬべきだから。子は親を所有すべきでない。子が死んでも親は生きているべきだから。

use std::rc::{Rc, Weak};
use std::cell::RefCell;
#[derive(Debug)]
struct Node {
    value: i32,
    parent: RefCell<Weak<Node>>,
    children: RefCell<Vec<Rc<Node>>>,
}
fn main() {
    let leaf = Rc::new(Node {
        value: 3,
        parent: RefCell::new(Weak::new()),
        children: RefCell::new(vec![]),
    });
    println!("leaf parent = {:?}", leaf.parent.borrow().upgrade());
    let branch = Rc::new(Node {
        value: 5,
        parent: RefCell::new(Weak::new()),
        children: RefCell::new(vec![Rc::clone(&leaf)]),
    });
    *leaf.parent.borrow_mut() = Rc::downgrade(&branch);
    println!("leaf parent = {:?}", leaf.parent.borrow().upgrade());
}
$ rustc main.rs
$ ./main
leaf parent = None
leaf parent = Some(Node { value: 5, parent: RefCell { value: (Weak) }, children: RefCell { value: [Node { value: 3, parent: RefCell { value: (Weak) }, children: RefCell { value: [] } }] } })
  • Rc.downgrade(): 引数の弱参照を返す
  • Weak.upgrade(): 値がまだ存在するならOption<Rc<T>>を返す(Some)。ないならNone

参照カウンタを可視化する

fn main() {
    let leaf = Rc::new(Node {
        value: 3,
        parent: RefCell::new(Weak::new()),
        children: RefCell::new(vec![]),
    });
    println!(
        "leaf strong = {}, weak = {}",
        Rc::strong_count(&leaf),
        Rc::weak_count(&leaf),
    );
    {
        let branch = Rc::new(Node {
            value: 5,
            parent: RefCell::new(Weak::new()),
            children: RefCell::new(vec![Rc::clone(&leaf)]),
        });
        *leaf.parent.borrow_mut() = Rc::downgrade(&branch);
        println!(
            "branch strong = {}, weak = {}",
            Rc::strong_count(&branch),
            Rc::weak_count(&branch),
        );
        println!(
            "leaf strong = {}, weak = {}",
            Rc::strong_count(&leaf),
            Rc::weak_count(&leaf),
        );
    }
    println!("leaf parent = {:?}", leaf.parent.borrow().upgrade());
    println!(
        "leaf strong = {}, weak = {}",
        Rc::strong_count(&leaf),
        Rc::weak_count(&leaf),
    );
}
  • Rc::strong_count(): 引数で指定した強参照のカウンタを返す
  • Rc::weak_count(): 引数で指定した弱参照のカウンタを返す
$ rustc main.rs
$ ./main
leaf strong = 1, weak = 0
branch strong = 1, weak = 1
leaf strong = 2, weak = 0
leaf parent = None
leaf strong = 1, weak = 0

 親branchが途中で死んだら子leafもそこで死ぬ。

 leafの参照カウンタは以下。

経緯
leaf作成直後 1 0
branchを生成してその子としてleafを紐付ける 2 0
branchの死後 1 0

資料

 独自のスマートポインタを作りたいなら詳細は以下。

所感

 ダメだ頭いたい。難しすぎる。自分で思い通りのコードが書ける気がしない。どんどん複雑化して付いていけない。頭がスマートでないWeakな私には厳しい。

対象環境

$ uname -a
Linux raspberrypi 4.19.42-v7+ #1219 SMP Tue May 14 21:20:58 BST 2019 armv7l GNU/Linux

前回まで