tree_cursor/
lib.rs

1//! # Summary
2//!
3//! This crate provides a simple, non-intrusive tree cursor that supports node
4//! mutation without [`Cell`]/[`RefCell`].
5//!
6//! # Soundness
7//!
8//! **In a nutshell: I don't know if this crate is sound. I *think* it is, but
9//! you should still check it yourself before using it.**
10//!
11//! The current implementation uses unsafe code to work around the borrow
12//! checker's strictness about lifetimes. So far, I haven't formally proven
13//! that it's memory-safe, especially in the presence of malicious code. I've
14//! done my best to write adversarial tests, and I'll continue poking at corner
15//! cases until I'm personally satisfied that it's sound, but it's not likely
16//! I'll get around to formally proving it until I better understand the borrow
17//! checker.
18//!
19//! # Concepts
20//!
21//! For the purposes of this crate...
22//! - a tree has exactly one root node (no tree is empty)
23//! - each node has zero or more children
24//! - "up" is toward the root
25//! - "down" is away from the root
26//! - at any point in time, a cursor has exactly one *active* node, also called
27//!   the cursor's *position*
28//!
29//! # Guided tour
30//!
31//! Let's look at a simple tree and how you might use a cursor to traverse it.
32//!
33//! ```
34//! use tree_cursor::cursor::TreeCursor;
35//! use tree_cursor::prelude::*;
36//!
37//! struct Node(&'static str, Vec<Node>);
38//!
39//! // This trait impl is used by TreeCursor::down to determine the next child
40//! // to visit. You can create a TreeCursor for something that doesn't
41//! // implement Down; you just won't be able to call TreeCursor::down.
42//! impl Down for Node {
43//!     fn down(&self, idx: usize) -> Option<&Self> {
44//!         // idx starts at 0 when we visit this node going downward and
45//!         // increments every time we visit it going upward. This allows us
46//!         // to visit each child in order by going back up to this node after
47//!         // each one.
48//!         self.1.get(idx)
49//!     }
50//! }
51//!
52//! let foobar = Node("foo", vec![
53//!     Node("bar", vec![]),
54//!     Node("zup", vec![]),
55//! ]);
56//! // foobar is a tree; its root, "foo", has two children, named "bar" and
57//! // "zup".
58//!
59//! let mut cur = TreeCursor::new(&foobar);
60//! assert_eq!(cur.get().0, "foo"); // The cursor starts at the root, "foo".
61//! // TreeCursor::get() returns a reference to the active node.
62//!
63//! assert!(cur.down());
64//! // TreeCursor::down returns true if the position actually moved.
65//! assert_eq!(cur.get().0, "bar");
66//!
67//! assert!(!cur.down());
68//! // "bar" has no children so we can't move the cursor down.
69//!
70//! assert!(cur.up());
71//! assert_eq!(cur.get().0, "foo"); // The cursor is at the root again.
72//!
73//! assert!(cur.down());
74//! assert_eq!(cur.get().0, "zup");
75//!
76//! assert!(cur.up());
77//! assert_eq!(cur.get().0, "foo"); // Back at the root.
78//!
79//! assert!(!cur.down()); // No more children to visit.
80//! assert!(!cur.up()); // The root has no parent.
81//! ```
82//!
83//! When the cursor is created, its initial position is at the root of the
84//! tree. [`down`] and [`up`] are two methods for repositioning the cursor.
85//! There are several methods besides [`down`] for moving down a tree;
86//! typically [`down`] is used when fully traversing a tree, as we just saw.
87//! Here are two ways you might do a full traversal if you don't know the
88//! tree's exact shape ahead of time:
89//!
90//! ```
91//! # use tree_cursor::cursor::TreeCursor;
92//! # use tree_cursor::prelude::*;
93//! #
94//! # struct Node(&'static str, Vec<Node>);
95//! #
96//! # impl Down for Node {
97//! #     fn down(&self, idx: usize) -> Option<&Self> {
98//! #         self.1.get(idx)
99//! #     }
100//! # }
101//! #
102//! # fn process_node(_: &Node) { }
103//! #
104//! fn process_tree_pre_order(root: &Node) {
105//!     let mut cur = TreeCursor::new(root);
106//!     'outer: loop {
107//!         process_node(cur.get());
108//!         while !cur.down() {
109//!             if !cur.up() { break 'outer; }
110//!         }
111//!     }
112//! }
113//!
114//! fn process_tree_post_order(root: &Node) {
115//!     let mut cur = TreeCursor::new(root);
116//!     loop {
117//!         while cur.down() { }
118//!         process_node(cur.get());
119//!         if !cur.up() { break; }
120//!     }
121//! }
122//! ```
123//!
124//! When you need more complex behavior or when there's no particular order to
125//! a node's children, you can use the [`down_map`] method instead, passing it
126//! a closure that determines the next child to visit.
127//!
128//! # Mutability and node references
129//!
130//! [`TreeCursor`] is the immutable version of the tree cursor, meaning it
131//! holds a shared reference to the tree, preventing you from modifying the
132//! tree until the cursor goes out of scope. If you need to modify the tree,
133//! use [`TreeCursorMut`] instead, which gives you access to a mutable
134//! reference to the active node.
135//!
136//! [`Cell`]: std::cell::Cell
137//! [`RefCell`]: std::cell::RefCell
138//!
139//! [`TreeCursor`]: cursor::TreeCursor
140//! [`TreeCursorMut`]: cursor::TreeCursorMut
141//! [`down`]: cursor::TreeCursor::down
142//! [`down_map`]: cursor::TreeCursor::down_map
143//! [`up`]: cursor::TreeCursor::up
144//! [`get`]: cursor::TreeCursor::get
145//! [`get_mut`]: cursor::TreeCursorMut::get_mut
146
147pub mod cursor;
148
149pub mod prelude {
150    pub use super::{Down, DownMut};
151}
152
153#[cfg(test)]
154mod tests;
155
156pub trait Down {
157    /// See [`TreeCursor::down`].
158    ///
159    /// [`TreeCursor::down`]: cursor::TreeCursor::down
160    fn down(&self, idx: usize) -> Option<&Self>;
161}
162
163pub trait DownMut {
164    /// See [`TreeCursorMut::down`].
165    ///
166    /// [`TreeCursorMut::down`]: cursor::TreeCursorMut::down
167    fn down_mut(&mut self, idx: usize) -> Option<&mut Self>;
168}