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}