Skip to main content

scirs2_core/data_structures/
rrb_tree.rs

1//! Persistent vector backed by a Relaxed Radix-Balanced (RRB) tree.
2//!
3//! All "mutating" operations return a *new version* of the vector while leaving
4//! the original intact.  Unchanged subtrees are shared via `Arc`, so the
5//! amortised cost of each operation is O(log₃₂ N) **node allocations** rather
6//! than O(N).
7//!
8//! # Branching factor
9//!
10//! Each internal node holds up to [`BRANCHING`] (32) children.  The tree height
11//! grows by one when the number of elements exceeds 32^(height+1).  Index
12//! navigation uses 5-bit radix steps.
13//!
14//! # Operations
15//!
16//! | Operation     | Allocation cost | Notes                              |
17//! |---------------|-----------------|------------------------------------|
18//! | `get`         | O(1)            | no allocation                      |
19//! | `push_back`   | O(log N)        | amortised, O(1) common case        |
20//! | `pop_back`    | O(log N)        |                                    |
21//! | `update`      | O(log N)        | path-copy from root to leaf        |
22//! | `iter`        | O(1)            | lazy traversal via stack           |
23//!
24//! # Example
25//!
26//! ```rust
27//! use scirs2_core::data_structures::PersistentVec;
28//!
29//! let v0 = PersistentVec::new();
30//! let v1 = v0.push_back(10u32);
31//! let v2 = v1.push_back(20u32);
32//! let v3 = v2.update(0, 99u32).expect("index 0 is valid");
33//!
34//! assert_eq!(v3.get(0), Some(&99u32));
35//! assert_eq!(v3.get(1), Some(&20u32));
36//! // Original v2 is unchanged:
37//! assert_eq!(v2.get(0), Some(&10u32));
38//! ```
39
40use std::sync::Arc;
41
42// ─────────────────────────────────────────────────────────────────────────────
43// Constants
44// ─────────────────────────────────────────────────────────────────────────────
45
46/// Branching factor: each internal node has up to 32 children.
47pub const BRANCHING: usize = 32;
48
49/// log₂(BRANCHING): bits consumed per tree level during radix navigation.
50pub const LOG_BRANCHING: usize = 5;
51
52/// Bitmask for extracting a single radix level index.
53const MASK: usize = BRANCHING - 1;
54
55// ─────────────────────────────────────────────────────────────────────────────
56// Internal node types
57// ─────────────────────────────────────────────────────────────────────────────
58
59/// A node in the RRB trie.
60///
61/// - `Internal`: holds up to `BRANCHING` child nodes.
62/// - `Leaf`: holds up to `BRANCHING` data elements.
63#[derive(Clone)]
64pub(crate) enum Node<T: Clone> {
65    Internal(Vec<Arc<Node<T>>>),
66    Leaf(Vec<T>),
67}
68
69impl<T: Clone> Node<T> {
70    // ------------------------------------------------------------------
71    // Navigation helpers
72    // ------------------------------------------------------------------
73
74    /// Access the child nodes of an internal node.
75    fn children(&self) -> &[Arc<Node<T>>] {
76        match self {
77            Node::Internal(ch) => ch,
78            Node::Leaf(_) => &[],
79        }
80    }
81
82    /// Access the data elements of a leaf node.
83    fn elements(&self) -> &[T] {
84        match self {
85            Node::Leaf(elems) => elems,
86            Node::Internal(_) => &[],
87        }
88    }
89
90    // ------------------------------------------------------------------
91    // Pure reads
92    // ------------------------------------------------------------------
93
94    /// Retrieve the element at `index` within the subtree rooted here.
95    ///
96    /// `shift` is `height * LOG_BRANCHING` — the number of bits by which the
97    /// radix index for this level begins.  When `shift == 0` this is a leaf.
98    fn get_at(&self, index: usize, shift: usize) -> Option<&T> {
99        match self {
100            Node::Leaf(elems) => elems.get(index & MASK),
101            Node::Internal(children) => {
102                let child_idx = (index >> shift) & MASK;
103                let child = children.get(child_idx)?;
104                if shift == LOG_BRANCHING {
105                    // Next level is a leaf.
106                    child.get_at(index, 0)
107                } else {
108                    child.get_at(index, shift - LOG_BRANCHING)
109                }
110            }
111        }
112    }
113
114    // ------------------------------------------------------------------
115    // Path-copy update
116    // ------------------------------------------------------------------
117
118    /// Return a new node that has `value` at `index`, sharing all unchanged
119    /// subtrees with `self`.  Returns `None` if `index` is out of bounds.
120    fn update_at(&self, index: usize, value: T, shift: usize) -> Option<Arc<Node<T>>> {
121        match self {
122            Node::Leaf(elems) => {
123                let i = index & MASK;
124                if i >= elems.len() {
125                    return None;
126                }
127                let mut new_elems = elems.clone();
128                new_elems[i] = value;
129                Some(Arc::new(Node::Leaf(new_elems)))
130            }
131            Node::Internal(children) => {
132                let child_idx = (index >> shift) & MASK;
133                if child_idx >= children.len() {
134                    return None;
135                }
136                let next_shift = shift.saturating_sub(LOG_BRANCHING);
137                let new_child = children[child_idx].update_at(index, value, next_shift)?;
138                let mut new_children = children.clone();
139                new_children[child_idx] = new_child;
140                Some(Arc::new(Node::Internal(new_children)))
141            }
142        }
143    }
144
145    // ------------------------------------------------------------------
146    // Insertion helpers
147    // ------------------------------------------------------------------
148
149    /// Attempt to insert `leaf_node` as the rightmost leaf of the subtree
150    /// rooted here, given that this subtree has height `height` (leaf == 0).
151    ///
152    /// Returns `None` when the subtree is full (cannot accept more leaves at
153    /// this height), indicating the caller must grow the tree upward.
154    fn push_tail(&self, leaf_node: Arc<Node<T>>, height: usize) -> Option<Arc<Node<T>>> {
155        match self {
156            Node::Leaf(_) => {
157                // Leaf nodes cannot hold another leaf — the tree needs to grow.
158                None
159            }
160            Node::Internal(children) => {
161                if height == 1 {
162                    // Direct parent of leaves.
163                    if children.len() < BRANCHING {
164                        let mut new_children = children.clone();
165                        new_children.push(leaf_node);
166                        Some(Arc::new(Node::Internal(new_children)))
167                    } else {
168                        None // this internal node is full
169                    }
170                } else {
171                    // Try to insert into the rightmost child first.
172                    if let Some(last) = children.last() {
173                        if let Some(new_last) = last.push_tail(Arc::clone(&leaf_node), height - 1) {
174                            let mut new_children = children.clone();
175                            let last_idx = new_children.len() - 1;
176                            new_children[last_idx] = new_last;
177                            return Some(Arc::new(Node::Internal(new_children)));
178                        }
179                    }
180                    // Rightmost subtree was full; create a new child path.
181                    if children.len() < BRANCHING {
182                        let new_subtree = build_path_to_leaf(leaf_node, height - 1);
183                        let mut new_children = children.clone();
184                        new_children.push(new_subtree);
185                        Some(Arc::new(Node::Internal(new_children)))
186                    } else {
187                        None // this internal node is also full
188                    }
189                }
190            }
191        }
192    }
193
194    // ------------------------------------------------------------------
195    // Deletion helpers
196    // ------------------------------------------------------------------
197
198    /// Remove the rightmost leaf from the subtree, returning the updated
199    /// subtree root and the removed leaf.
200    ///
201    /// Returns `None` for the new root if the subtree becomes empty.
202    fn pop_tail(&self, height: usize) -> Option<(Option<Arc<Node<T>>>, Arc<Node<T>>)> {
203        match self {
204            Node::Leaf(_) => {
205                // A leaf is itself the rightmost "leaf subtree".
206                None
207            }
208            Node::Internal(children) => {
209                if children.is_empty() {
210                    return None;
211                }
212                let last_idx = children.len() - 1;
213
214                if height == 1 {
215                    // Children are leaves.
216                    let removed_leaf = Arc::clone(&children[last_idx]);
217                    let new_root = if children.len() == 1 {
218                        None
219                    } else {
220                        let new_children = children[..last_idx].to_vec();
221                        Some(Arc::new(Node::Internal(new_children)))
222                    };
223                    Some((new_root, removed_leaf))
224                } else {
225                    let (new_last_opt, leaf) = children[last_idx].pop_tail(height - 1)?;
226                    let new_children = match new_last_opt {
227                        Some(new_last) => {
228                            let mut ch = children.clone();
229                            ch[last_idx] = new_last;
230                            ch
231                        }
232                        None => children[..last_idx].to_vec(),
233                    };
234                    let new_root = if new_children.is_empty() {
235                        None
236                    } else {
237                        Some(Arc::new(Node::Internal(new_children)))
238                    };
239                    Some((new_root, leaf))
240                }
241            }
242        }
243    }
244}
245
246// ─────────────────────────────────────────────────────────────────────────────
247// Private helpers
248// ─────────────────────────────────────────────────────────────────────────────
249
250/// Build a spine of internal nodes of `height` levels leading down to `leaf`.
251///
252/// Height 0 → return `leaf` unchanged.
253/// Height 1 → one internal node containing `leaf`.
254/// Height 2 → one internal node containing one internal node containing `leaf`.
255fn build_path_to_leaf<T: Clone>(leaf: Arc<Node<T>>, height: usize) -> Arc<Node<T>> {
256    if height == 0 {
257        return leaf;
258    }
259    let child = build_path_to_leaf(leaf, height - 1);
260    Arc::new(Node::Internal(vec![child]))
261}
262
263// ─────────────────────────────────────────────────────────────────────────────
264// PersistentVec<T>
265// ─────────────────────────────────────────────────────────────────────────────
266
267/// Persistent vector with structural sharing, backed by an RRB-tree.
268///
269/// All "mutating" operations return a new version while the original is left
270/// unchanged.  Structural sharing (via `Arc`) ensures that only O(log N)
271/// nodes are newly allocated per operation.
272///
273/// ```rust
274/// use scirs2_core::data_structures::PersistentVec;
275///
276/// let v0: PersistentVec<u32> = PersistentVec::new();
277/// let v1 = v0.push_back(1);
278/// let v2 = v1.push_back(2);
279///
280/// assert_eq!(v2.len(), 2);
281/// assert_eq!(v2.get(0), Some(&1));
282/// // v1 is still valid and unchanged:
283/// assert_eq!(v1.len(), 1);
284/// ```
285#[derive(Clone)]
286pub struct PersistentVec<T: Clone> {
287    /// Root of the trie, or `None` when empty.
288    root: Option<Arc<Node<T>>>,
289    /// Total number of elements.
290    size: usize,
291    /// `shift = height * LOG_BRANCHING` for the current tree.
292    /// A brand-new (empty) vector starts with `shift = LOG_BRANCHING` (height 1).
293    shift: usize,
294    /// Tail buffer: up to BRANCHING elements not yet pushed into the trie.
295    tail: Vec<T>,
296}
297
298impl<T: Clone> PersistentVec<T> {
299    // ------------------------------------------------------------------
300    // Constructors
301    // ------------------------------------------------------------------
302
303    /// Creates an empty persistent vector.
304    pub fn new() -> Self {
305        PersistentVec {
306            root: None,
307            size: 0,
308            shift: LOG_BRANCHING,
309            tail: Vec::new(),
310        }
311    }
312
313    // ------------------------------------------------------------------
314    // Queries
315    // ------------------------------------------------------------------
316
317    /// Returns the number of elements.
318    pub fn len(&self) -> usize {
319        self.size
320    }
321
322    /// Returns `true` when the vector contains no elements.
323    pub fn is_empty(&self) -> bool {
324        self.size == 0
325    }
326
327    /// Returns a reference to the element at `index`, or `None` if out of
328    /// bounds.
329    ///
330    /// O(log N) time, no allocation.
331    pub fn get(&self, index: usize) -> Option<&T> {
332        if index >= self.size {
333            return None;
334        }
335
336        // Is the element in the tail?
337        let tail_offset = self.size - self.tail.len();
338        if index >= tail_offset {
339            return self.tail.get(index - tail_offset);
340        }
341
342        // Navigate the trie.
343        let root = self.root.as_ref()?;
344        root.get_at(index, self.shift)
345    }
346
347    // ------------------------------------------------------------------
348    // Persistent updates
349    // ------------------------------------------------------------------
350
351    /// Returns a new vector with `value` appended to the end.
352    ///
353    /// Amortised O(log N) — O(1) when the tail has room.
354    pub fn push_back(&self, value: T) -> Self {
355        if self.tail.len() < BRANCHING {
356            // Fast path: just extend the tail.
357            let mut new_tail = self.tail.clone();
358            new_tail.push(value);
359            return PersistentVec {
360                root: self.root.clone(),
361                size: self.size + 1,
362                shift: self.shift,
363                tail: new_tail,
364            };
365        }
366
367        // Tail is full; push it into the trie as a leaf node.
368        let full_tail = std::mem::take(&mut { self.tail.clone() });
369        let leaf = Arc::new(Node::Leaf(full_tail));
370
371        let (new_root, new_shift) = match &self.root {
372            None => {
373                // Empty trie — the leaf becomes the sole node.
374                (Some(Arc::new(Node::Internal(vec![leaf]))), self.shift)
375            }
376            Some(root) => {
377                let height = self.shift / LOG_BRANCHING;
378                match root.push_tail(leaf.clone(), height) {
379                    Some(new_root) => (Some(new_root), self.shift),
380                    None => {
381                        // Tree is full at current height; grow it.
382                        let new_height = height + 1;
383                        let new_shift = new_height * LOG_BRANCHING;
384                        let new_spine = build_path_to_leaf(leaf, height);
385                        let new_root = Arc::new(Node::Internal(vec![Arc::clone(root), new_spine]));
386                        (Some(new_root), new_shift)
387                    }
388                }
389            }
390        };
391
392        PersistentVec {
393            root: new_root,
394            size: self.size + 1,
395            shift: new_shift,
396            tail: vec![value],
397        }
398    }
399
400    /// Returns a new vector with the element at `index` replaced by `value`.
401    ///
402    /// Returns `None` if `index >= self.len()`.
403    /// O(log N) node allocations.
404    pub fn update(&self, index: usize, value: T) -> Option<Self> {
405        if index >= self.size {
406            return None;
407        }
408
409        let tail_offset = self.size - self.tail.len();
410
411        if index >= tail_offset {
412            // Element is in the tail.
413            let mut new_tail = self.tail.clone();
414            new_tail[index - tail_offset] = value;
415            return Some(PersistentVec {
416                root: self.root.clone(),
417                size: self.size,
418                shift: self.shift,
419                tail: new_tail,
420            });
421        }
422
423        // Element is in the trie — path-copy from root to the leaf.
424        let root = self.root.as_ref()?;
425        let new_root = root.update_at(index, value, self.shift)?;
426        Some(PersistentVec {
427            root: Some(new_root),
428            size: self.size,
429            shift: self.shift,
430            tail: self.tail.clone(),
431        })
432    }
433
434    /// Removes and returns the last element, returning `(new_vec, value)`.
435    ///
436    /// Returns `None` if the vector is empty.
437    /// O(log N) in the worst case, O(1) common case.
438    pub fn pop_back(&self) -> Option<(Self, T)> {
439        if self.size == 0 {
440            return None;
441        }
442
443        if !self.tail.is_empty() {
444            // Fast path: pop from tail.
445            let mut new_tail = self.tail.clone();
446            let popped = new_tail.pop()?;
447
448            if new_tail.is_empty() && self.root.is_some() {
449                // Tail is now empty; pull the rightmost leaf from the trie
450                // to become the new tail.
451                let root = self.root.as_ref()?;
452                let height = self.shift / LOG_BRANCHING;
453                let (new_root_opt, leaf_arc) = root.pop_tail(height)?;
454
455                let new_tail_vec = leaf_arc.elements().to_vec();
456
457                // Shrink tree height if the new root has only one child.
458                let (final_root, final_shift) = match new_root_opt {
459                    None => (None, LOG_BRANCHING),
460                    Some(nr) => {
461                        let (collapsed, new_shift) = collapse_root(nr, self.shift);
462                        (Some(collapsed), new_shift)
463                    }
464                };
465
466                return Some((
467                    PersistentVec {
468                        root: final_root,
469                        size: self.size - 1,
470                        shift: final_shift,
471                        tail: new_tail_vec,
472                    },
473                    popped,
474                ));
475            }
476
477            return Some((
478                PersistentVec {
479                    root: self.root.clone(),
480                    size: self.size - 1,
481                    shift: self.shift,
482                    tail: new_tail,
483                },
484                popped,
485            ));
486        }
487
488        // Tail is empty; pull from trie (should not normally happen but handle
489        // the degenerate case).
490        let root = self.root.as_ref()?;
491        let height = self.shift / LOG_BRANCHING;
492        let (new_root_opt, leaf_arc) = root.pop_tail(height)?;
493
494        let leaf_elems = leaf_arc.elements();
495        let popped = leaf_elems.last()?.clone();
496        let new_tail_vec: Vec<T> = leaf_elems[..leaf_elems.len() - 1].to_vec();
497
498        let (final_root, final_shift) = match new_root_opt {
499            None => (None, LOG_BRANCHING),
500            Some(nr) => {
501                let (collapsed, ns) = collapse_root(nr, self.shift);
502                (Some(collapsed), ns)
503            }
504        };
505
506        Some((
507            PersistentVec {
508                root: final_root,
509                size: self.size - 1,
510                shift: final_shift,
511                tail: new_tail_vec,
512            },
513            popped,
514        ))
515    }
516
517    // ------------------------------------------------------------------
518    // Iteration
519    // ------------------------------------------------------------------
520
521    /// Returns an iterator over references to elements in order.
522    pub fn iter(&self) -> PersistentVecIter<T> {
523        // Collect all elements from the trie via depth-first traversal.
524        let mut trie_elements: Vec<T> =
525            Vec::with_capacity(self.size.saturating_sub(self.tail.len()));
526        if let Some(root) = &self.root {
527            collect_elements(root, &mut trie_elements);
528        }
529        // Append tail.
530        trie_elements.extend_from_slice(&self.tail);
531
532        PersistentVecIter {
533            data: trie_elements,
534            pos: 0,
535        }
536    }
537}
538
539/// Collapse the root if it has exactly one internal child (shrink height).
540fn collapse_root<T: Clone>(root: Arc<Node<T>>, shift: usize) -> (Arc<Node<T>>, usize) {
541    if shift <= LOG_BRANCHING {
542        return (root, shift);
543    }
544    match root.as_ref() {
545        Node::Internal(children) if children.len() == 1 => {
546            collapse_root(Arc::clone(&children[0]), shift - LOG_BRANCHING)
547        }
548        _ => (root, shift),
549    }
550}
551
552/// Depth-first traversal to collect all elements from a subtree.
553fn collect_elements<T: Clone>(node: &Arc<Node<T>>, out: &mut Vec<T>) {
554    match node.as_ref() {
555        Node::Leaf(elems) => out.extend_from_slice(elems),
556        Node::Internal(children) => {
557            for child in children {
558                collect_elements(child, out);
559            }
560        }
561    }
562}
563
564// ─────────────────────────────────────────────────────────────────────────────
565// Default / FromIterator
566// ─────────────────────────────────────────────────────────────────────────────
567
568impl<T: Clone> Default for PersistentVec<T> {
569    fn default() -> Self {
570        Self::new()
571    }
572}
573
574impl<T: Clone> FromIterator<T> for PersistentVec<T> {
575    fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
576        let mut vec = PersistentVec::new();
577        for item in iter {
578            vec = vec.push_back(item);
579        }
580        vec
581    }
582}
583
584// ─────────────────────────────────────────────────────────────────────────────
585// Iterator
586// ─────────────────────────────────────────────────────────────────────────────
587
588/// Iterator over references to elements of a `PersistentVec`.
589///
590/// Produced by [`PersistentVec::iter`].
591pub struct PersistentVecIter<T: Clone> {
592    /// All elements in order (built eagerly during construction for simplicity).
593    data: Vec<T>,
594    pos: usize,
595}
596
597impl<T: Clone> Iterator for PersistentVecIter<T> {
598    type Item = T;
599
600    fn next(&mut self) -> Option<Self::Item> {
601        if self.pos < self.data.len() {
602            let item = self.data[self.pos].clone();
603            self.pos += 1;
604            Some(item)
605        } else {
606            None
607        }
608    }
609
610    fn size_hint(&self) -> (usize, Option<usize>) {
611        let remaining = self.data.len() - self.pos;
612        (remaining, Some(remaining))
613    }
614}
615
616impl<T: Clone> ExactSizeIterator for PersistentVecIter<T> {}
617
618// ─────────────────────────────────────────────────────────────────────────────
619// Tests
620// ─────────────────────────────────────────────────────────────────────────────
621
622#[cfg(test)]
623mod tests {
624    use super::*;
625
626    #[test]
627    fn test_rrb_basic_ops() {
628        let mut v = PersistentVec::new();
629        for i in 0u32..100 {
630            v = v.push_back(i);
631        }
632        assert_eq!(v.len(), 100);
633        for i in 0u32..100 {
634            assert_eq!(v.get(i as usize), Some(&i), "mismatch at index {i}");
635        }
636        assert_eq!(v.get(100), None);
637    }
638
639    #[test]
640    fn test_rrb_persistence() {
641        let v0: PersistentVec<u32> = PersistentVec::new();
642        let v1 = v0.push_back(1);
643        let v2 = v1.push_back(2);
644        let v3 = v2.push_back(3);
645
646        // Newer versions see all elements.
647        assert_eq!(v3.len(), 3);
648        assert_eq!(v3.get(0), Some(&1));
649        assert_eq!(v3.get(1), Some(&2));
650        assert_eq!(v3.get(2), Some(&3));
651
652        // Older versions are unmodified.
653        assert_eq!(v0.len(), 0);
654        assert_eq!(v1.len(), 1);
655        assert_eq!(v1.get(0), Some(&1));
656        assert_eq!(v2.len(), 2);
657        assert_eq!(v2.get(1), Some(&2));
658    }
659
660    #[test]
661    fn test_rrb_update() {
662        let base: PersistentVec<i32> = (0..50).collect();
663
664        let updated = base.update(25, 999).expect("index 25 is valid");
665
666        // Updated version has the new value.
667        assert_eq!(updated.get(25), Some(&999));
668        // All other indices are unchanged.
669        for i in 0..50 {
670            if i != 25 {
671                assert_eq!(updated.get(i), Some(&(i as i32)), "mismatch at {i}");
672            }
673        }
674        // Original is unchanged.
675        assert_eq!(base.get(25), Some(&25));
676
677        // Out-of-bounds update returns None.
678        assert!(base.update(100, 42).is_none());
679    }
680
681    #[test]
682    fn test_rrb_iter() {
683        let v: PersistentVec<u32> = (0..64).collect();
684        let collected: Vec<u32> = v.iter().collect();
685        let expected: Vec<u32> = (0..64).collect();
686        assert_eq!(collected, expected);
687    }
688
689    #[test]
690    fn test_rrb_large() {
691        const N: usize = 1000;
692        let v: PersistentVec<usize> = (0..N).collect();
693        assert_eq!(v.len(), N);
694        for i in 0..N {
695            assert_eq!(v.get(i), Some(&i), "mismatch at index {i}");
696        }
697    }
698
699    #[test]
700    fn test_rrb_pop_back() {
701        let v: PersistentVec<u32> = (0..10).collect();
702
703        let (v2, last) = v.pop_back().expect("non-empty");
704        assert_eq!(last, 9);
705        assert_eq!(v2.len(), 9);
706
707        // Original unchanged.
708        assert_eq!(v.len(), 10);
709        assert_eq!(v.get(9), Some(&9));
710
711        // Pop until empty.
712        let mut cur = v.clone();
713        for expected_last in (0..10).rev() {
714            let (next, val) = cur.pop_back().expect("should not be empty");
715            assert_eq!(val, expected_last);
716            cur = next;
717        }
718        assert!(cur.is_empty());
719        assert!(cur.pop_back().is_none());
720    }
721
722    #[test]
723    fn test_rrb_from_iterator() {
724        let v: PersistentVec<u8> = vec![10u8, 20, 30, 40, 50].into_iter().collect();
725        assert_eq!(v.len(), 5);
726        assert_eq!(v.get(2), Some(&30u8));
727    }
728
729    #[test]
730    fn test_rrb_default_is_empty() {
731        let v: PersistentVec<f64> = PersistentVec::default();
732        assert!(v.is_empty());
733        assert_eq!(v.len(), 0);
734    }
735
736    #[test]
737    fn test_rrb_cross_leaf_boundary() {
738        // Push exactly BRANCHING+1 elements to force a leaf spill.
739        let n = BRANCHING + 1;
740        let v: PersistentVec<usize> = (0..n).collect();
741        assert_eq!(v.len(), n);
742        for i in 0..n {
743            assert_eq!(v.get(i), Some(&i), "mismatch at {i}");
744        }
745    }
746
747    #[test]
748    fn test_rrb_update_across_leaf_boundary() {
749        // Cover the code path where an update targets an element in the trie
750        // (not the tail) near a leaf boundary.
751        let v: PersistentVec<u32> = (0..100).collect();
752        // BRANCHING = 32; index 31 is the last element of the first trie leaf.
753        let updated = v.update(31, 777).expect("valid index");
754        assert_eq!(updated.get(31), Some(&777));
755        assert_eq!(v.get(31), Some(&31)); // original unchanged
756    }
757}