Skip to main content

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}