Skip to main content

reify_graph/
lib.rs

1#![deny(unsafe_code)]
2
3//! # reify-graph
4//!
5//! Safe library to convert `Rc<RefCell<T>>`-based pointer graphs into
6//! node-indexed adjacency representations and back.
7//!
8//! This enables serialization, inspection, and transformation of cyclic
9//! and DAG-structured data that would otherwise be difficult to work with.
10//!
11//! # Examples
12//!
13//! ```
14//! use reify_graph::{reify_graph, reflect_graph};
15//! use std::cell::RefCell;
16//! use std::rc::Rc;
17//!
18//! // A simple tree node
19//! #[derive(Clone, Debug)]
20//! struct Node {
21//!     value: i32,
22//!     children: Vec<Rc<RefCell<Node>>>,
23//! }
24//!
25//! let leaf = Rc::new(RefCell::new(Node { value: 1, children: vec![] }));
26//! let root = Rc::new(RefCell::new(Node {
27//!     value: 0,
28//!     children: vec![leaf.clone()],
29//! }));
30//!
31//! // Reify the graph
32//! let graph = reify_graph(root.clone(), |n| n.children.clone());
33//! assert_eq!(graph.nodes.len(), 2);
34//! assert_eq!(graph.edges.len(), 1);
35//!
36//! // Reconstruct the graph
37//! let reconstructed = reflect_graph(graph, |n, kids| n.children = kids);
38//! assert_eq!(reconstructed.borrow().value, 0);
39//! assert_eq!(reconstructed.borrow().children.len(), 1);
40//! assert_eq!(reconstructed.borrow().children[0].borrow().value, 1);
41//! ```
42
43use std::cell::RefCell;
44use std::collections::HashMap;
45use std::rc::Rc;
46
47pub mod arc;
48
49/// A stable node identifier derived from pointer identity.
50///
51/// # Examples
52///
53/// ```
54/// use reify_graph::NodeId;
55///
56/// let id = NodeId(42);
57/// assert_eq!(id.0, 42);
58/// ```
59#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
60#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
61pub struct NodeId(pub usize);
62
63/// An adjacency-list representation of a pointer graph.
64///
65/// Contains all nodes with their data, directed edges between nodes,
66/// and the identifier of the root node.
67///
68/// # Examples
69///
70/// ```
71/// use reify_graph::{ReifiedGraph, NodeId};
72///
73/// let graph = ReifiedGraph {
74///     nodes: vec![(NodeId(0), "root"), (NodeId(1), "child")],
75///     edges: vec![(NodeId(0), NodeId(1))],
76///     root: NodeId(0),
77/// };
78/// assert_eq!(graph.nodes.len(), 2);
79/// ```
80#[derive(Debug, Clone)]
81#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
82pub struct ReifiedGraph<T> {
83    /// The nodes in the graph, each paired with its stable identifier.
84    pub nodes: Vec<(NodeId, T)>,
85    /// Directed edges from parent to child.
86    pub edges: Vec<(NodeId, NodeId)>,
87    /// The root node's identifier.
88    pub root: NodeId,
89}
90
91/// Extract a [`NodeId`] from an `Rc<RefCell<T>>` using pointer identity.
92///
93/// # Examples
94///
95/// ```
96/// use reify_graph::{node_id_of, NodeId};
97/// use std::cell::RefCell;
98/// use std::rc::Rc;
99///
100/// let a = Rc::new(RefCell::new(42));
101/// let b = a.clone();
102/// assert_eq!(node_id_of(&a), node_id_of(&b));
103/// ```
104pub fn node_id_of<T>(rc: &Rc<RefCell<T>>) -> NodeId {
105    NodeId(Rc::as_ptr(rc) as *const () as usize)
106}
107
108/// Collect all reachable nodes from `root`, detecting shared pointers.
109///
110/// Returns a map from [`NodeId`] to the corresponding `Rc<RefCell<T>>`.
111///
112/// # Examples
113///
114/// ```
115/// use reify_graph::collect_nodes;
116/// use std::cell::RefCell;
117/// use std::rc::Rc;
118///
119/// #[derive(Clone)]
120/// struct Node { children: Vec<Rc<RefCell<Node>>> }
121///
122/// let leaf = Rc::new(RefCell::new(Node { children: vec![] }));
123/// let root = Rc::new(RefCell::new(Node { children: vec![leaf.clone()] }));
124///
125/// let nodes = collect_nodes(&root, &|n: &Node| n.children.clone());
126/// assert_eq!(nodes.len(), 2);
127/// ```
128pub fn collect_nodes<T, F>(root: &Rc<RefCell<T>>, children: &F) -> HashMap<NodeId, Rc<RefCell<T>>>
129where
130    F: Fn(&T) -> Vec<Rc<RefCell<T>>>,
131{
132    let mut visited = HashMap::new();
133    collect_nodes_inner(root, children, &mut visited);
134    visited
135}
136
137fn collect_nodes_inner<T, F>(
138    node: &Rc<RefCell<T>>,
139    children: &F,
140    visited: &mut HashMap<NodeId, Rc<RefCell<T>>>,
141) where
142    F: Fn(&T) -> Vec<Rc<RefCell<T>>>,
143{
144    let id = node_id_of(node);
145    if visited.contains_key(&id) {
146        return;
147    }
148    visited.insert(id, node.clone());
149
150    let kids = children(&node.borrow());
151    for kid in &kids {
152        collect_nodes_inner(kid, children, visited);
153    }
154}
155
156/// Reify an `Rc<RefCell<T>>` graph into a [`ReifiedGraph`].
157///
158/// The `children` closure extracts child pointers from a node's data.
159/// The resulting graph contains cloned node data and edges derived
160/// from pointer identity.
161///
162/// # Examples
163///
164/// ```
165/// use reify_graph::reify_graph;
166/// use std::cell::RefCell;
167/// use std::rc::Rc;
168///
169/// #[derive(Clone, Debug)]
170/// struct Node {
171///     value: i32,
172///     children: Vec<Rc<RefCell<Node>>>,
173/// }
174///
175/// let shared = Rc::new(RefCell::new(Node { value: 2, children: vec![] }));
176/// let root = Rc::new(RefCell::new(Node {
177///     value: 0,
178///     children: vec![
179///         Rc::new(RefCell::new(Node { value: 1, children: vec![shared.clone()] })),
180///         shared.clone(),
181///     ],
182/// }));
183///
184/// let graph = reify_graph(root, |n| n.children.clone());
185/// // 3 unique nodes: root(0), child(1), shared(2)
186/// assert_eq!(graph.nodes.len(), 3);
187/// ```
188pub fn reify_graph<T, F>(root: Rc<RefCell<T>>, children: F) -> ReifiedGraph<T>
189where
190    F: Fn(&T) -> Vec<Rc<RefCell<T>>>,
191    T: Clone,
192{
193    let all_nodes = collect_nodes(&root, &children);
194    let root_id = node_id_of(&root);
195
196    let mut nodes = Vec::with_capacity(all_nodes.len());
197    let mut edges = Vec::new();
198
199    for (id, rc) in &all_nodes {
200        let borrowed = rc.borrow();
201        nodes.push((*id, borrowed.clone()));
202
203        for kid in children(&borrowed) {
204            let kid_id = node_id_of(&kid);
205            edges.push((*id, kid_id));
206        }
207    }
208
209    ReifiedGraph {
210        nodes,
211        edges,
212        root: root_id,
213    }
214}
215
216/// Reconstruct an `Rc<RefCell<T>>` graph from a [`ReifiedGraph`].
217///
218/// The `set_children` closure wires up child pointers on a node given
219/// its reconstructed children.
220///
221/// # Examples
222///
223/// ```
224/// use reify_graph::{reify_graph, reflect_graph};
225/// use std::cell::RefCell;
226/// use std::rc::Rc;
227///
228/// #[derive(Clone, Debug)]
229/// struct Node {
230///     value: i32,
231///     children: Vec<Rc<RefCell<Node>>>,
232/// }
233///
234/// let leaf = Rc::new(RefCell::new(Node { value: 1, children: vec![] }));
235/// let root = Rc::new(RefCell::new(Node { value: 0, children: vec![leaf] }));
236///
237/// let graph = reify_graph(root, |n| n.children.clone());
238/// let rebuilt = reflect_graph(graph, |n, kids| n.children = kids);
239///
240/// assert_eq!(rebuilt.borrow().value, 0);
241/// assert_eq!(rebuilt.borrow().children[0].borrow().value, 1);
242/// ```
243pub fn reflect_graph<T, F>(graph: ReifiedGraph<T>, set_children: F) -> Rc<RefCell<T>>
244where
245    F: Fn(&mut T, Vec<Rc<RefCell<T>>>),
246    T: Clone,
247{
248    // Build Rc<RefCell<T>> for each node (without children wired up yet)
249    let mut rc_map: HashMap<NodeId, Rc<RefCell<T>>> = HashMap::new();
250    for (id, data) in &graph.nodes {
251        rc_map.insert(*id, Rc::new(RefCell::new(data.clone())));
252    }
253
254    // Build adjacency: parent -> [child_ids] preserving edge order
255    let mut adj: HashMap<NodeId, Vec<NodeId>> = HashMap::new();
256    for (from, to) in &graph.edges {
257        adj.entry(*from).or_default().push(*to);
258    }
259
260    // Wire up children
261    for (id, rc) in &rc_map {
262        if let Some(child_ids) = adj.get(id) {
263            let children: Vec<Rc<RefCell<T>>> =
264                child_ids.iter().map(|cid| rc_map[cid].clone()).collect();
265            set_children(&mut rc.borrow_mut(), children);
266        }
267    }
268
269    rc_map[&graph.root].clone()
270}
271
272#[cfg(test)]
273mod tests {
274    use super::*;
275
276    #[derive(Clone, Debug, PartialEq)]
277    struct Node {
278        value: i32,
279        children: Vec<Rc<RefCell<Node>>>,
280    }
281
282    fn children_of(n: &Node) -> Vec<Rc<RefCell<Node>>> {
283        n.children.clone()
284    }
285
286    fn set_children_of(n: &mut Node, kids: Vec<Rc<RefCell<Node>>>) {
287        n.children = kids;
288    }
289
290    #[test]
291    fn node_id_identity() {
292        let a = Rc::new(RefCell::new(Node {
293            value: 1,
294            children: vec![],
295        }));
296        let b = a.clone();
297        assert_eq!(node_id_of(&a), node_id_of(&b));
298
299        let c = Rc::new(RefCell::new(Node {
300            value: 1,
301            children: vec![],
302        }));
303        assert_ne!(node_id_of(&a), node_id_of(&c));
304    }
305
306    #[test]
307    fn collect_nodes_simple_linked_list() {
308        let c = Rc::new(RefCell::new(Node {
309            value: 2,
310            children: vec![],
311        }));
312        let b = Rc::new(RefCell::new(Node {
313            value: 1,
314            children: vec![c.clone()],
315        }));
316        let a = Rc::new(RefCell::new(Node {
317            value: 0,
318            children: vec![b.clone()],
319        }));
320
321        let nodes = collect_nodes(&a, &children_of);
322        assert_eq!(nodes.len(), 3);
323    }
324
325    #[test]
326    fn collect_nodes_with_cycle() {
327        let a = Rc::new(RefCell::new(Node {
328            value: 0,
329            children: vec![],
330        }));
331        let b = Rc::new(RefCell::new(Node {
332            value: 1,
333            children: vec![a.clone()],
334        }));
335        // Create cycle: a -> b -> a
336        a.borrow_mut().children.push(b.clone());
337
338        let nodes = collect_nodes(&a, &children_of);
339        assert_eq!(nodes.len(), 2);
340    }
341
342    #[test]
343    fn reify_dag_with_shared_children() {
344        // DAG: root -> {a, b, shared}, a -> {shared}, b -> {shared}
345        let shared = Rc::new(RefCell::new(Node {
346            value: 99,
347            children: vec![],
348        }));
349        let a = Rc::new(RefCell::new(Node {
350            value: 1,
351            children: vec![shared.clone()],
352        }));
353        let b = Rc::new(RefCell::new(Node {
354            value: 2,
355            children: vec![shared.clone()],
356        }));
357        let root = Rc::new(RefCell::new(Node {
358            value: 0,
359            children: vec![a.clone(), b.clone(), shared.clone()],
360        }));
361
362        let graph = reify_graph(root, children_of);
363
364        // 4 unique nodes
365        assert_eq!(graph.nodes.len(), 4);
366        // root->a, root->b, root->shared, a->shared, b->shared = 5 edges
367        assert_eq!(graph.edges.len(), 5);
368    }
369
370    #[test]
371    fn reify_five_node_dag() {
372        // n0 -> {n1, n2}, n1 -> {n3, n4}, n2 -> {n3}, n3 -> {n4}
373        let n4 = Rc::new(RefCell::new(Node {
374            value: 4,
375            children: vec![],
376        }));
377        let n3 = Rc::new(RefCell::new(Node {
378            value: 3,
379            children: vec![n4.clone()],
380        }));
381        let n2 = Rc::new(RefCell::new(Node {
382            value: 2,
383            children: vec![n3.clone()],
384        }));
385        let n1 = Rc::new(RefCell::new(Node {
386            value: 1,
387            children: vec![n3.clone(), n4.clone()],
388        }));
389        let n0 = Rc::new(RefCell::new(Node {
390            value: 0,
391            children: vec![n1.clone(), n2.clone()],
392        }));
393
394        let graph = reify_graph(n0, children_of);
395        assert_eq!(graph.nodes.len(), 5);
396        // n0->n1, n0->n2, n1->n3, n1->n4, n2->n3, n3->n4 = 6 edges
397        assert_eq!(graph.edges.len(), 6);
398    }
399
400    #[test]
401    fn round_trip_reify_reflect() {
402        let leaf1 = Rc::new(RefCell::new(Node {
403            value: 10,
404            children: vec![],
405        }));
406        let leaf2 = Rc::new(RefCell::new(Node {
407            value: 20,
408            children: vec![],
409        }));
410        let root = Rc::new(RefCell::new(Node {
411            value: 0,
412            children: vec![leaf1, leaf2],
413        }));
414
415        let graph = reify_graph(root.clone(), children_of);
416        let rebuilt = reflect_graph(graph, set_children_of);
417
418        assert_eq!(rebuilt.borrow().value, 0);
419        assert_eq!(rebuilt.borrow().children.len(), 2);
420
421        let mut child_values: Vec<i32> = rebuilt
422            .borrow()
423            .children
424            .iter()
425            .map(|c| c.borrow().value)
426            .collect();
427        child_values.sort();
428        assert_eq!(child_values, vec![10, 20]);
429    }
430
431    #[test]
432    fn round_trip_preserves_sharing() {
433        // shared node should be the same Rc in the reconstruction
434        let shared = Rc::new(RefCell::new(Node {
435            value: 42,
436            children: vec![],
437        }));
438        let root = Rc::new(RefCell::new(Node {
439            value: 0,
440            children: vec![shared.clone(), shared.clone()],
441        }));
442
443        let graph = reify_graph(root, children_of);
444        let rebuilt = reflect_graph(graph, set_children_of);
445
446        let children = &rebuilt.borrow().children;
447        assert_eq!(children.len(), 2);
448        // Both children should point to the same Rc
449        assert_eq!(
450            Rc::as_ptr(&children[0]) as usize,
451            Rc::as_ptr(&children[1]) as usize
452        );
453    }
454}