reify_graph/lib.rs
1#![deny(unsafe_code)]
2
3//! Convert `Rc<RefCell<T>>` and `Arc<Mutex<T>>` pointer graphs into a flat,
4//! inspectable, serializable form, and back.
5//!
6//! Pointer-shaped data (trees with shared subtrees, DAGs, cyclic graphs)
7//! is awkward to serialize, log, or transform: walking it naively either
8//! duplicates shared nodes or loops forever on cycles. This crate gives
9//! you a small, non-invasive API to:
10//!
11//! - **Reify**: turn a pointer graph rooted at an `Rc<RefCell<T>>` (or
12//! `Arc<Mutex<T>>`) into a flat [`ReifiedGraph`] of nodes and edges,
13//! keyed by pointer identity, detecting cycles and preserving sharing.
14//! - **Reflect**: rebuild the original pointer graph from a [`ReifiedGraph`],
15//! restoring the same sharing topology (one allocation per node id, even
16//! when many edges point to it).
17//!
18//! With the default `serde` feature enabled, [`ReifiedGraph`] is
19//! `Serialize` + `Deserialize`, which gives you JSON / postcard / etc.
20//! serialization of arbitrary cyclic data essentially for free.
21//!
22//! # When to use it
23//!
24//! - You want to dump or log a graph for debugging (an AST with shared
25//! sub-expressions, a circuit netlist, a scene graph).
26//! - You want to send pointer-shaped state across a process boundary.
27//! - You want to apply a structural transform (renumbering, GC, deduping)
28//! that's awkward on the live `Rc` graph but easy on a flat node+edge
29//! form.
30//!
31//! # Design choices
32//!
33//! - Node identity comes from [`Rc::as_ptr`] (or `Arc::as_ptr`) cast to
34//! `usize`. No traits or wrappers required on your `T`.
35//! - Children are extracted by a closure you supply, so the library never
36//! guesses at the structure of your type.
37//! - Node data is cloned into the [`ReifiedGraph`] (use `Arc<Inner>` inside
38//! `T` if cloning is expensive).
39//! - Reconstruction in [`reflect_graph`] preserves sharing exactly: each
40//! [`NodeId`] becomes one [`Rc`] and is re-pointed everywhere it was
41//! referenced.
42//!
43//! See the [`docs/phase2-graph-reification.md`][phase2] design note, and
44//! the [`serialize_graph` example][example] for an end-to-end `serde_json`
45//! round trip.
46//!
47//! [phase2]: https://github.com/joshburgess/reify-reflect/blob/main/docs/phase2-graph-reification.md
48//! [example]: https://github.com/joshburgess/reify-reflect/blob/main/reify-graph/examples/serialize_graph.rs
49//!
50//! # Examples
51//!
52//! Round-trip a small `Rc<RefCell<_>>` tree through the flat form:
53//!
54//! ```
55//! use reify_graph::{reify_graph, reflect_graph};
56//! use std::cell::RefCell;
57//! use std::rc::Rc;
58//!
59//! #[derive(Clone, Debug)]
60//! struct Node {
61//! value: i32,
62//! children: Vec<Rc<RefCell<Node>>>,
63//! }
64//!
65//! let leaf = Rc::new(RefCell::new(Node { value: 1, children: vec![] }));
66//! let root = Rc::new(RefCell::new(Node {
67//! value: 0,
68//! children: vec![leaf.clone()],
69//! }));
70//!
71//! // Flatten: 2 nodes, 1 edge
72//! let graph = reify_graph(root.clone(), |n| n.children.clone());
73//! assert_eq!(graph.nodes.len(), 2);
74//! assert_eq!(graph.edges.len(), 1);
75//!
76//! // Rebuild, restoring the original sharing
77//! let reconstructed = reflect_graph(graph, |n, kids| n.children = kids);
78//! assert_eq!(reconstructed.borrow().value, 0);
79//! assert_eq!(reconstructed.borrow().children[0].borrow().value, 1);
80//! ```
81//!
82//! For `Arc<Mutex<T>>` graphs (the same pattern, thread-safe), see the
83//! [`arc`] module's `reify_graph_arc` and `reflect_graph_arc`.
84
85use std::cell::RefCell;
86use std::collections::HashMap;
87use std::rc::Rc;
88
89pub mod arc;
90
91/// A stable node identifier derived from pointer identity.
92///
93/// # Examples
94///
95/// ```
96/// use reify_graph::NodeId;
97///
98/// let id = NodeId(42);
99/// assert_eq!(id.0, 42);
100/// ```
101#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
102#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
103pub struct NodeId(pub usize);
104
105/// An adjacency-list representation of a pointer graph.
106///
107/// Contains all nodes with their data, directed edges between nodes,
108/// and the identifier of the root node.
109///
110/// # Examples
111///
112/// ```
113/// use reify_graph::{ReifiedGraph, NodeId};
114///
115/// let graph = ReifiedGraph {
116/// nodes: vec![(NodeId(0), "root"), (NodeId(1), "child")],
117/// edges: vec![(NodeId(0), NodeId(1))],
118/// root: NodeId(0),
119/// };
120/// assert_eq!(graph.nodes.len(), 2);
121/// ```
122#[derive(Debug, Clone)]
123#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
124pub struct ReifiedGraph<T> {
125 /// The nodes in the graph, each paired with its stable identifier.
126 pub nodes: Vec<(NodeId, T)>,
127 /// Directed edges from parent to child.
128 pub edges: Vec<(NodeId, NodeId)>,
129 /// The root node's identifier.
130 pub root: NodeId,
131}
132
133/// Extract a [`NodeId`] from an `Rc<RefCell<T>>` using pointer identity.
134///
135/// # Examples
136///
137/// ```
138/// use reify_graph::{node_id_of, NodeId};
139/// use std::cell::RefCell;
140/// use std::rc::Rc;
141///
142/// let a = Rc::new(RefCell::new(42));
143/// let b = a.clone();
144/// assert_eq!(node_id_of(&a), node_id_of(&b));
145/// ```
146pub fn node_id_of<T>(rc: &Rc<RefCell<T>>) -> NodeId {
147 NodeId(Rc::as_ptr(rc) as *const () as usize)
148}
149
150/// Collect all reachable nodes from `root`, detecting shared pointers.
151///
152/// Returns a map from [`NodeId`] to the corresponding `Rc<RefCell<T>>`.
153///
154/// # Examples
155///
156/// ```
157/// use reify_graph::collect_nodes;
158/// use std::cell::RefCell;
159/// use std::rc::Rc;
160///
161/// #[derive(Clone)]
162/// struct Node { children: Vec<Rc<RefCell<Node>>> }
163///
164/// let leaf = Rc::new(RefCell::new(Node { children: vec![] }));
165/// let root = Rc::new(RefCell::new(Node { children: vec![leaf.clone()] }));
166///
167/// let nodes = collect_nodes(&root, &|n: &Node| n.children.clone());
168/// assert_eq!(nodes.len(), 2);
169/// ```
170pub fn collect_nodes<T, F>(root: &Rc<RefCell<T>>, children: &F) -> HashMap<NodeId, Rc<RefCell<T>>>
171where
172 F: Fn(&T) -> Vec<Rc<RefCell<T>>>,
173{
174 let mut visited = HashMap::new();
175 collect_nodes_inner(root, children, &mut visited);
176 visited
177}
178
179fn collect_nodes_inner<T, F>(
180 node: &Rc<RefCell<T>>,
181 children: &F,
182 visited: &mut HashMap<NodeId, Rc<RefCell<T>>>,
183) where
184 F: Fn(&T) -> Vec<Rc<RefCell<T>>>,
185{
186 let id = node_id_of(node);
187 if visited.contains_key(&id) {
188 return;
189 }
190 visited.insert(id, node.clone());
191
192 let kids = children(&node.borrow());
193 for kid in &kids {
194 collect_nodes_inner(kid, children, visited);
195 }
196}
197
198/// Reify an `Rc<RefCell<T>>` graph into a [`ReifiedGraph`].
199///
200/// The `children` closure extracts child pointers from a node's data.
201/// The resulting graph contains cloned node data and edges derived
202/// from pointer identity.
203///
204/// # Examples
205///
206/// ```
207/// use reify_graph::reify_graph;
208/// use std::cell::RefCell;
209/// use std::rc::Rc;
210///
211/// #[derive(Clone, Debug)]
212/// struct Node {
213/// value: i32,
214/// children: Vec<Rc<RefCell<Node>>>,
215/// }
216///
217/// let shared = Rc::new(RefCell::new(Node { value: 2, children: vec![] }));
218/// let root = Rc::new(RefCell::new(Node {
219/// value: 0,
220/// children: vec![
221/// Rc::new(RefCell::new(Node { value: 1, children: vec![shared.clone()] })),
222/// shared.clone(),
223/// ],
224/// }));
225///
226/// let graph = reify_graph(root, |n| n.children.clone());
227/// // 3 unique nodes: root(0), child(1), shared(2)
228/// assert_eq!(graph.nodes.len(), 3);
229/// ```
230pub fn reify_graph<T, F>(root: Rc<RefCell<T>>, children: F) -> ReifiedGraph<T>
231where
232 F: Fn(&T) -> Vec<Rc<RefCell<T>>>,
233 T: Clone,
234{
235 let all_nodes = collect_nodes(&root, &children);
236 let root_id = node_id_of(&root);
237
238 let mut nodes = Vec::with_capacity(all_nodes.len());
239 let mut edges = Vec::new();
240
241 for (id, rc) in &all_nodes {
242 let borrowed = rc.borrow();
243 nodes.push((*id, borrowed.clone()));
244
245 for kid in children(&borrowed) {
246 let kid_id = node_id_of(&kid);
247 edges.push((*id, kid_id));
248 }
249 }
250
251 ReifiedGraph {
252 nodes,
253 edges,
254 root: root_id,
255 }
256}
257
258/// Reconstruct an `Rc<RefCell<T>>` graph from a [`ReifiedGraph`].
259///
260/// The `set_children` closure wires up child pointers on a node given
261/// its reconstructed children.
262///
263/// # Examples
264///
265/// ```
266/// use reify_graph::{reify_graph, reflect_graph};
267/// use std::cell::RefCell;
268/// use std::rc::Rc;
269///
270/// #[derive(Clone, Debug)]
271/// struct Node {
272/// value: i32,
273/// children: Vec<Rc<RefCell<Node>>>,
274/// }
275///
276/// let leaf = Rc::new(RefCell::new(Node { value: 1, children: vec![] }));
277/// let root = Rc::new(RefCell::new(Node { value: 0, children: vec![leaf] }));
278///
279/// let graph = reify_graph(root, |n| n.children.clone());
280/// let rebuilt = reflect_graph(graph, |n, kids| n.children = kids);
281///
282/// assert_eq!(rebuilt.borrow().value, 0);
283/// assert_eq!(rebuilt.borrow().children[0].borrow().value, 1);
284/// ```
285pub fn reflect_graph<T, F>(graph: ReifiedGraph<T>, set_children: F) -> Rc<RefCell<T>>
286where
287 F: Fn(&mut T, Vec<Rc<RefCell<T>>>),
288 T: Clone,
289{
290 // Build Rc<RefCell<T>> for each node (without children wired up yet)
291 let mut rc_map: HashMap<NodeId, Rc<RefCell<T>>> = HashMap::new();
292 for (id, data) in &graph.nodes {
293 rc_map.insert(*id, Rc::new(RefCell::new(data.clone())));
294 }
295
296 // Build adjacency: parent -> [child_ids] preserving edge order
297 let mut adj: HashMap<NodeId, Vec<NodeId>> = HashMap::new();
298 for (from, to) in &graph.edges {
299 adj.entry(*from).or_default().push(*to);
300 }
301
302 // Wire up children
303 for (id, rc) in &rc_map {
304 if let Some(child_ids) = adj.get(id) {
305 let children: Vec<Rc<RefCell<T>>> =
306 child_ids.iter().map(|cid| rc_map[cid].clone()).collect();
307 set_children(&mut rc.borrow_mut(), children);
308 }
309 }
310
311 rc_map[&graph.root].clone()
312}
313
314#[cfg(test)]
315mod tests {
316 use super::*;
317
318 #[derive(Clone, Debug, PartialEq)]
319 struct Node {
320 value: i32,
321 children: Vec<Rc<RefCell<Node>>>,
322 }
323
324 fn children_of(n: &Node) -> Vec<Rc<RefCell<Node>>> {
325 n.children.clone()
326 }
327
328 fn set_children_of(n: &mut Node, kids: Vec<Rc<RefCell<Node>>>) {
329 n.children = kids;
330 }
331
332 #[test]
333 fn node_id_identity() {
334 let a = Rc::new(RefCell::new(Node {
335 value: 1,
336 children: vec![],
337 }));
338 let b = a.clone();
339 assert_eq!(node_id_of(&a), node_id_of(&b));
340
341 let c = Rc::new(RefCell::new(Node {
342 value: 1,
343 children: vec![],
344 }));
345 assert_ne!(node_id_of(&a), node_id_of(&c));
346 }
347
348 #[test]
349 fn collect_nodes_simple_linked_list() {
350 let c = Rc::new(RefCell::new(Node {
351 value: 2,
352 children: vec![],
353 }));
354 let b = Rc::new(RefCell::new(Node {
355 value: 1,
356 children: vec![c.clone()],
357 }));
358 let a = Rc::new(RefCell::new(Node {
359 value: 0,
360 children: vec![b.clone()],
361 }));
362
363 let nodes = collect_nodes(&a, &children_of);
364 assert_eq!(nodes.len(), 3);
365 }
366
367 #[test]
368 fn collect_nodes_with_cycle() {
369 let a = Rc::new(RefCell::new(Node {
370 value: 0,
371 children: vec![],
372 }));
373 let b = Rc::new(RefCell::new(Node {
374 value: 1,
375 children: vec![a.clone()],
376 }));
377 // Create cycle: a -> b -> a
378 a.borrow_mut().children.push(b.clone());
379
380 let nodes = collect_nodes(&a, &children_of);
381 assert_eq!(nodes.len(), 2);
382 }
383
384 #[test]
385 fn reify_dag_with_shared_children() {
386 // DAG: root -> {a, b, shared}, a -> {shared}, b -> {shared}
387 let shared = Rc::new(RefCell::new(Node {
388 value: 99,
389 children: vec![],
390 }));
391 let a = Rc::new(RefCell::new(Node {
392 value: 1,
393 children: vec![shared.clone()],
394 }));
395 let b = Rc::new(RefCell::new(Node {
396 value: 2,
397 children: vec![shared.clone()],
398 }));
399 let root = Rc::new(RefCell::new(Node {
400 value: 0,
401 children: vec![a.clone(), b.clone(), shared.clone()],
402 }));
403
404 let graph = reify_graph(root, children_of);
405
406 // 4 unique nodes
407 assert_eq!(graph.nodes.len(), 4);
408 // root->a, root->b, root->shared, a->shared, b->shared = 5 edges
409 assert_eq!(graph.edges.len(), 5);
410 }
411
412 #[test]
413 fn reify_five_node_dag() {
414 // n0 -> {n1, n2}, n1 -> {n3, n4}, n2 -> {n3}, n3 -> {n4}
415 let n4 = Rc::new(RefCell::new(Node {
416 value: 4,
417 children: vec![],
418 }));
419 let n3 = Rc::new(RefCell::new(Node {
420 value: 3,
421 children: vec![n4.clone()],
422 }));
423 let n2 = Rc::new(RefCell::new(Node {
424 value: 2,
425 children: vec![n3.clone()],
426 }));
427 let n1 = Rc::new(RefCell::new(Node {
428 value: 1,
429 children: vec![n3.clone(), n4.clone()],
430 }));
431 let n0 = Rc::new(RefCell::new(Node {
432 value: 0,
433 children: vec![n1.clone(), n2.clone()],
434 }));
435
436 let graph = reify_graph(n0, children_of);
437 assert_eq!(graph.nodes.len(), 5);
438 // n0->n1, n0->n2, n1->n3, n1->n4, n2->n3, n3->n4 = 6 edges
439 assert_eq!(graph.edges.len(), 6);
440 }
441
442 #[test]
443 fn round_trip_reify_reflect() {
444 let leaf1 = Rc::new(RefCell::new(Node {
445 value: 10,
446 children: vec![],
447 }));
448 let leaf2 = Rc::new(RefCell::new(Node {
449 value: 20,
450 children: vec![],
451 }));
452 let root = Rc::new(RefCell::new(Node {
453 value: 0,
454 children: vec![leaf1, leaf2],
455 }));
456
457 let graph = reify_graph(root.clone(), children_of);
458 let rebuilt = reflect_graph(graph, set_children_of);
459
460 assert_eq!(rebuilt.borrow().value, 0);
461 assert_eq!(rebuilt.borrow().children.len(), 2);
462
463 let mut child_values: Vec<i32> = rebuilt
464 .borrow()
465 .children
466 .iter()
467 .map(|c| c.borrow().value)
468 .collect();
469 child_values.sort();
470 assert_eq!(child_values, vec![10, 20]);
471 }
472
473 #[test]
474 fn round_trip_preserves_sharing() {
475 // shared node should be the same Rc in the reconstruction
476 let shared = Rc::new(RefCell::new(Node {
477 value: 42,
478 children: vec![],
479 }));
480 let root = Rc::new(RefCell::new(Node {
481 value: 0,
482 children: vec![shared.clone(), shared.clone()],
483 }));
484
485 let graph = reify_graph(root, children_of);
486 let rebuilt = reflect_graph(graph, set_children_of);
487
488 let children = &rebuilt.borrow().children;
489 assert_eq!(children.len(), 2);
490 // Both children should point to the same Rc
491 assert_eq!(
492 Rc::as_ptr(&children[0]) as usize,
493 Rc::as_ptr(&children[1]) as usize
494 );
495 }
496}