Dev Study
← Rust で小さなブラウザを作る コース

14. ノードを Rust で持つ — 親は Weak、子は Rc、付け外しは RefCell

は「親と子の順序付き集合を持つの木」です(DOM Standard 4.2)。これを Rust で持つときの壁は、子から親へも、親から子へも辿りたいのに、は一方向にしか持てないことです。参照実装は、子は <Node>(所有者が複数いてよい: 親と、JavaScript から掴んだ参照)、親は <Node>(所有しない参照)、子のリストと親の参照は で包む(&self のまま付け外しできる)、の 3 点で解いています。

親→子が Rc、子→親が Weak なのは、を避けるためです。両方 Rc にすると、親も子も互いに「まだ使われている」と数え合って、木を捨ててもメモリが解放されません。テストの dropping_the_tree_frees_nodes は、親の Rc を drop したら子の Weak が upgrade できなくなる(=解放された)ことを確かめています。

ノードは / (タグ名と)/ Text(文字列)の 3 種類です。append_child(parent, child) が child の parent を Weak で指し、parent の children に Rc を push します。() は子孫の Text をつないだもの、set_text_content(node, s) は「子を全部外して、空でなければ Text を 1 つ付ける」。これは DOM Standard の textContent の setter(string replace all)の手順そのままです。 は「子孫のうち (深さ優先・文書順)で最初の、ID が一致する要素」を返します。

下のコードは、もも使わずに木を手で組んで、親子の参照・Rc::strong_count・textContent・getElementById・解放を順に見ます。h1 の所有者が 2(変数と親の children)で、body の所有者が 1(子は Weak で指すので増えない)になるのが要点です。

本物のブラウザでは: の Node(core/dom/node.h)は parent_or_shadow_host_node_・previous_・next_ の 3 つのポインタを持ち、ContainerNode が first_child_ と last_child_ を足します。子は連結リストなので「n 番目の子は O(N)」と README にあります。この回の <> と Weak<Node> の親より、ポインタで兄弟をつなぐ形です。寿命の管理は という Blink 専用のトレース GC で、GarbageCollected<T> をし、参照は Member<T>(強)と WeakMember<T>(弱)で持ち、各クラスが Trace(Visitor*) で自分の参照を列挙します。Rc / Weak / RefCell で手で寿命を決めるこの実装と、GC に任せる Blink の差がここです。Firefox の nsINode は参照カウント + (xpcom/base/nsCycleCollector.cpp)で循環を回収します。

開発のどこに活かすか: 双方向に辿る木やグラフ(組織図、依存関係、UI のコンポーネント木、JSON の親参照つきの走査)を Rust で持つときの定番がこれです。他言語の GC が隠している「誰が誰を生かしているか」を Rc / Weak で明示する経験は、メモリリークの原因(循環参照)を見つける目になります。

点線の付いた語はタップすると説明が出ます。用語集を開く →

① 矢印の種類で所有を分ける

<body><h1>Hi</h1></body> のメモリ上の関係
変数 body: Rc<Node>強い参照 1
children: RefCell<Vec<Rc<Node>>>(強い参照)
Node bodyElement { tag: "body" }
children(強い参照)↓ / parent: RefCell<Weak<Node>>(弱い参照)↑
Node h1Element { tag: "h1" }
children(強い参照)↓ / parent(弱い参照)↑
Node #textText("Hi")
  • ・強い参照(Rc)が 0 になったノードは解放される。弱い参照(Weak)は数に入らない
  • ・だから body を捨てれば木全体が消え、子が親を指していても循環にならない

RefCell は「借用の検査をコンパイル時ではなく実行時にする」箱です。&self のメソッドから children を書き換えられるのはこのためです。

▶ サンプルコードを実行してみよう(自由に書き換えてOK)

Rust

手元で動かす(配布物)

図やコードに出てくる用語(タップで説明)

サンプルコードで身につける(3本) →

学んだ概念を、素朴な例から実務寄りの例まで3つの具体例で確認します。

公式ドキュメントで詳しく ↗