サンプルコードで身につける: ノードを Rust で持つ — 親は Weak、子は Rc、付け外しは RefCell
解説で学んだ概念を、3つの具体例で確認します。素朴な例から実務寄りの例へと進みます。
1Rc の子・Weak の親・RefCell の子リスト
ツリーを Rust で持つ定番の 3 点セットです。親→子は (所有)、子→親は (所有しない)なので循環せず、子のリストは に包んで &self のまま追加できます。実行すると body の strong_count が 2(変数と親の children)、html の strong_count が 1 のままと出て、html を drop すると body から見た親が None になり、Weak の upgrade も失敗することが確かめられます。
// ツリーを Rust で持つ定番: 子は Rc(所有者が複数になりうる)、親は Weak(循環参照を避ける)、
// 子のリストは RefCell(&self のまま付け外しする = 内部可変性)。
use std::cell::RefCell;
use std::rc::{Rc, Weak};
pub type NodeRef = Rc<Node>;
#[derive(Debug)]
pub struct Node {
pub tag: String,
parent: RefCell<Weak<Node>>,
children: RefCell<Vec<NodeRef>>,
}
impl Node {
pub fn element(tag: &str) -> NodeRef {
Rc::new(Node {
tag: tag.to_string(),
parent: RefCell::new(Weak::new()),
children: RefCell::new(Vec::new()),
})
}
/// DOM の appendChild。子の parent を書き、親の children に積む
pub fn append_child(parent: &NodeRef, child: NodeRef) {
*child.parent.borrow_mut() = Rc::downgrade(parent);
parent.children.borrow_mut().push(child);
}
/// Weak を upgrade して親を取り出す。親が既に消えていれば None
pub fn parent(&self) -> Option<NodeRef> {
self.parent.borrow().upgrade()
}
}
fn main() {
let html = Node::element("html");
let body = Node::element("body");
Node::append_child(&html, body.clone());
println!("body の親: {}", body.parent().unwrap().tag);
println!("html の親: {:?}", html.parent().map(|p| p.tag.clone()));
// 変数 body と、html の children の 2 か所が body を所有している
println!("body の strong_count: {}", Rc::strong_count(&body));
// 子→親は Weak なので、html の strong_count は 1 のまま(循環していない)
println!("html の strong_count: {}", Rc::strong_count(&html));
// 親を drop すると、子から見た親は None になる(メモリは解放されている)
let watch = Rc::downgrade(&html);
drop(html);
println!("html を drop した後、body の親: {:?}", body.parent().map(|p| p.tag.clone()));
println!("html はまだ生きているか: {}", watch.upgrade().is_some());
}
2textContent と getElementById を DOM Standard の定義どおりに書く
の getter は子孫の Text をつないだもの、setter は子を全部外して を 1 つ付けるだけ( Standard の string replace all)、 は (深さ優先で文書に書いた順)で最初の、という 3 つの定義をそのまま Rust にしました。実行すると getElementById("y") が兄弟の <p id="y"> ではなく深い方の <span id="y"> を返すこと、setter の後は子が 1 つになり外された span の親が無くなること、空文字を入れると子が 0 になることが出ます。
// textContent と getElementById を DOM Standard の定義どおりに書く。getter は子孫の Text をつないだもの、
// setter("string replace all")は子を全部外して Text を 1 つ付ける、getElementById は tree order で最初の要素。
use std::cell::RefCell;
use std::rc::{Rc, Weak};
pub type NodeRef = Rc<Node>;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum NodeKind {
Element { tag: String, id: Option<String> }, // 属性は id だけに絞った
Text(String),
}
#[derive(Debug)]
pub struct Node {
pub kind: NodeKind,
parent: RefCell<Weak<Node>>,
children: RefCell<Vec<NodeRef>>,
}
impl Node {
fn new(kind: NodeKind) -> NodeRef {
Rc::new(Node { kind, parent: RefCell::new(Weak::new()), children: RefCell::new(Vec::new()) })
}
pub fn element(tag: &str, id: Option<&str>) -> NodeRef {
Node::new(NodeKind::Element { tag: tag.to_string(), id: id.map(|s| s.to_string()) })
}
pub fn text(data: &str) -> NodeRef {
Node::new(NodeKind::Text(data.to_string()))
}
pub fn append_child(parent: &NodeRef, child: NodeRef) {
*child.parent.borrow_mut() = Rc::downgrade(parent);
parent.children.borrow_mut().push(child);
}
pub fn id(&self) -> Option<&str> {
if let NodeKind::Element { id, .. } = &self.kind { id.as_deref() } else { None }
}
pub fn text_content(&self) -> String {
match &self.kind {
NodeKind::Text(t) => t.clone(),
NodeKind::Element { .. } => self.children.borrow().iter().map(|c| c.text_content()).collect(),
}
}
pub fn set_text_content(node: &NodeRef, value: &str) {
for c in node.children.borrow().iter() {
*c.parent.borrow_mut() = Weak::new(); // 外した子は親を失う
}
node.children.borrow_mut().clear();
if !value.is_empty() {
Node::append_child(node, Node::text(value));
}
}
pub fn get_element_by_id(root: &NodeRef, id: &str) -> Option<NodeRef> {
for child in root.children.borrow().iter() {
if child.id() == Some(id) {
return Some(child.clone());
}
if let Some(found) = Node::get_element_by_id(child, id) {
return Some(found); // 兄弟より先に、子の中を探し終える = tree order
}
}
None
}
}
fn main() {
// <div><p id="x">Hello, <span id="y">world</span></p><p id="y">second</p></div>
let div = Node::element("div", None);
let p1 = Node::element("p", Some("x"));
Node::append_child(&p1, Node::text("Hello, "));
let span = Node::element("span", Some("y"));
Node::append_child(&span, Node::text("world"));
Node::append_child(&p1, span.clone());
let p2 = Node::element("p", Some("y"));
Node::append_child(&p2, Node::text("second"));
Node::append_child(&div, p1.clone());
Node::append_child(&div, p2);
println!("div.textContent = {:?}", div.text_content());
let found = Node::get_element_by_id(&div, "y").unwrap();
println!("getElementById(\"y\") は span か: {} (深い方が先 = tree order)", Rc::ptr_eq(&found, &span));
Node::set_text_content(&p1, "replaced");
println!("setter の後: 子の数 = {}, textContent = {:?}", p1.children.borrow().len(), p1.text_content());
println!("外された span に親はあるか: {}", span.parent.borrow().upgrade().is_some());
Node::set_text_content(&p1, "");
println!("空文字を入れた後: 子の数 = {}", p1.children.borrow().len());
}
3実務に写す: 入れ子の設定を Rc / Weak で持ち、親を辿ってパスを作る
JSON のような入れ子の設定を同じ Rc / Weak / RefCell の木で持ち、葉から親を Weak で辿って server.tls.cert のようなを作ります。親を Weak にする理由は DOM と同じで、親→子→親の循環でメモリが解放されなくなるのを避けるためです。実行すると 3 つの葉のパスと値、cert の strong_count が 3 → 2 と変わる様子、根を捨てた後は親が消えて path() が "cert" だけになる様子が出ます。
// 実務に写す: 設定ファイルの入れ子(JSON のような木)を Rc / Weak / RefCell で持ち、
// 子から親へ辿って「どのキーの下にいるか」のパス(server.tls.cert)を作る。
// DOM で parent を Weak にしたのと同じ理由: 親→子→親の循環で解放されなくなるのを避ける。
use std::cell::RefCell;
use std::rc::{Rc, Weak};
pub type NodeRef = Rc<ConfigNode>;
#[derive(Debug)]
pub struct ConfigNode {
pub key: String,
pub value: Option<String>, // None なら「入れ子を持つ節」
parent: RefCell<Weak<ConfigNode>>,
children: RefCell<Vec<NodeRef>>,
}
impl ConfigNode {
pub fn section(key: &str) -> NodeRef {
ConfigNode::new(key, None)
}
pub fn leaf(key: &str, value: &str) -> NodeRef {
ConfigNode::new(key, Some(value.to_string()))
}
fn new(key: &str, value: Option<String>) -> NodeRef {
Rc::new(ConfigNode {
key: key.to_string(),
value,
parent: RefCell::new(Weak::new()),
children: RefCell::new(Vec::new()),
})
}
pub fn add(parent: &NodeRef, child: NodeRef) -> NodeRef {
*child.parent.borrow_mut() = Rc::downgrade(parent);
parent.children.borrow_mut().push(child.clone());
child
}
/// 親を Weak で辿って "server.tls.cert" を作る。根(キーが空)は含めない
pub fn path(&self) -> String {
let mut parts = vec![self.key.clone()];
let mut cur = self.parent.borrow().upgrade();
while let Some(p) = cur {
if !p.key.is_empty() {
parts.push(p.key.clone());
}
cur = p.parent.borrow().upgrade();
}
parts.reverse();
parts.join(".")
}
/// 葉を tree order で全部集める(DOM の find_all と同じ形)
pub fn leaves(node: &NodeRef, out: &mut Vec<NodeRef>) {
for c in node.children.borrow().iter() {
if c.value.is_some() {
out.push(c.clone());
}
ConfigNode::leaves(c, out);
}
}
}
fn main() {
let root = ConfigNode::section("");
let server = ConfigNode::add(&root, ConfigNode::section("server"));
ConfigNode::add(&server, ConfigNode::leaf("port", "8080"));
let tls = ConfigNode::add(&server, ConfigNode::section("tls"));
let cert = ConfigNode::add(&tls, ConfigNode::leaf("cert", "/etc/ssl/app.pem"));
ConfigNode::add(&root, ConfigNode::leaf("log_level", "info"));
let mut leaves = Vec::new();
ConfigNode::leaves(&root, &mut leaves);
for leaf in &leaves {
println!("{:<16} = {}", leaf.path(), leaf.value.as_deref().unwrap_or(""));
}
// cert は「変数 cert」「leaves の中」「tls の children」の 3 か所から所有されている
println!("cert の strong_count = {}", Rc::strong_count(&cert));
drop(leaves);
println!("leaves を捨てた後 = {}", Rc::strong_count(&cert));
drop(root);
drop(server);
drop(tls);
// 根を捨てても cert 自身は変数が握っているので生きている。ただし親は消えたので path は短くなる
println!("根を捨てた後の cert.path() = {:?}", cert.path());
}