W.I.S. Laboratory
menu-bar

Rust


RustでC++のstd::shared_ptrのようなことをしてみる

Rustのスマートポインタは原則的にユニークポインタを使うので、ポインタのコピーはできず、移動のみ行うことができる。
ただ、移動を明示しなくても代入や関数呼び出しで自動的に移動になるので、C++で例えるとstd::unique_ptrを他の変数に代入したり関数に渡したりすると勝手にstd::move()してくれるような感じだ。
ではC++のstd::shared_ptrやSwiftのARCのような、いわゆる参照カウント式のスマートポインタが使えないかというとそんなことはなく、Rustにも一応RcやArcという参照カウント式のスマートポインタが用意されている。

RcとArcの違いはスレッドセーフかどうかということで、RcはスレッドセーフではないがArcはスレッドセーフだ。
ArcのほうがRcよりもコストは高いようだが、ループ内でガンガン使わなければ体感で分かるほどではない。
ただしRustには「共有されているものに対しては書き込みができない」という原則があるので、RcとArcの参照先は基本的に書き込み不可能だ。
そこで、mutに関係なく書き込みが可能なユニークポインタであるRefCellをRc(またはArc)でラップすることで、書込み可能な参照カウント式スマートポインタもどきを作るというが定石になっている。
要するに「Rc<RefCell<T>>」だ。
CellやRefCellはBoxと違って、代入した変数をmutしていなくてもメソッドを使って書き込みが可能なので、こういった使い方に向いている。

C++やSwiftに比べると、正直「うーん・・」という書き方になる。
値を読む時も関数に投げるときも値を書き換えるときも、必ずメソッド経由になるのでスッとは書けない。(Rustに書きやすさを求めるのは何か間違っている気もするが)

例えば参照カウンタをインクリメントするときはclone()メソッドを呼び出す必要があるし、値を読みたいときはborrow()メソッドで借用して読み出し、値を書き込むときはborrow_mut()メソッドでミュータブル借用したメモリに書き込み、という感じになる。
実際のキータイプ数でいえば&mutで投げて&mutで受け取るほうが楽かもしれないが、それでもRustでミュータブルな参照カウント式ポインタが使えるというのはそれなりのメリットがありそうだ。
どうしてもユニークポインタでは実装できないというような局面では助けてくれそうな気がする。

なぜRefCellでなければいけないのか、BoxをRcでラップしても同様のことができるのではないかと思うかもしれないが、Rustのmut属性は変数名に対してのみ指定可能なので、ラップした中のBoxまで届かないのだ。
例えば、

let mut a = Rc::new(Box::new(Hoge { i:10, f:23.45, s:"Kitty".to_string() }));

と書いたとしても、上のmutはRcに対して働くだけで、その中のBoxには届かないのでBoxがイミュータブルになってしまう。
というわけでRefCellのお世話になるわけだ。


[ 戻る ]
saluteweb