tiny_trie/nibble_trie.rs
1//! Nibble Trie — a fixed-fanout radix trie indexed by nibbles (half-bytes).
2//!
3//! Each node has 16 child slots (one per nibble value 0–15), addressed by
4//! direct indexing rather than binary search or SIMD. This trades space for
5//! simplicity and lookup speed: no comparison loops, no branch misprediction
6//! on the child search path.
7//!
8//! # Terminal Nodes
9//!
10//! Keys that are prefixes of other keys (e.g. "ab" in {"ab", "abc"}) are
11//! represented by a `terminal` flag on the node where the key ends, rather
12//! than a null-byte leaf child. This eliminates null terminators, allows
13//! `0x00` bytes in keys, and makes `get()` accept plain `&[u8]`.
14//!
15//! # Empty-slot encoding (`OptNz`)
16//!
17//! Child slots and the `leaf` field use `OptNz<PTR>` — a `#[repr(transparent)]`
18//! newtype over `PTR` where the value `0` means "empty" and any nonzero value
19//! is a real arena index or key index. `[OptNz<PTR>; 16]` is layout-identical
20//! to `[PTR; 16]`, so the SIMD `children_mask` path is reused via a single
21//! `repr(transparent)` pointer cast. Real arena child addresses are `>= 1`
22//! (the root at arena[0] is never a child target) and real key indices are
23//! `>= 1` (index[0] is a dummy entry), so `0` is free as the sentinel.
24//!
25//! # Key Index Encoding
26//!
27//! Real keys start at index 1 (index 0 is the dummy entry pointing at buf[0],
28//! an unused byte). `values[i]` corresponds to `index[i+1]` (i.e. key index
29//! `ki` maps to `values[ki - 1]`).
30
31use crate::ByteKey;
32use crate::tiny_array::TinyArray;
33use std::{fmt, marker::PhantomData, num::NonZero, ops::{Bound, RangeBounds}, simd::{Simd, cmp::SimdPartialEq}};
34
35/// One slot of the sparse `index`: the buf offset (>= 1; buf[0] is the dummy byte),
36/// the key length, and the value inline. `None` slots are gaps.
37pub type Slot<LEN, T> = (NonZero<usize>, LEN, T);
38
39// ---------------------------------------------------------------------------
40// TrieIndex trait
41// ---------------------------------------------------------------------------
42
43/// Trait for types used as arena/key indices and prefix lengths in NibbleTrie.
44///
45/// Implemented for `u8`, `u16`, `u32`, and `u64`. The type parameter `PTR` (pointer
46/// type) controls the width of `children`, `leaf`, and arena indices. The type
47/// parameter `LEN` (length type) controls the width of `prefix_len` and key
48/// lengths in the index.
49pub trait TrieIndex: Copy + Clone + Default + PartialEq + Eq + fmt::Debug + 'static {
50 /// Convert to `usize` for indexing.
51 fn as_usize(self) -> usize;
52 /// Maximum representable value (e.g. `u16::MAX` for u16).
53 fn max_value() -> usize;
54 /// Zero value, used for initial values and as the `OptNz` empty sentinel.
55 fn zero() -> Self;
56 /// Maximum value used as sentinel for empty slots in `children[]` by the
57 /// sibling tries (`fixed_len_nibble_trie`, `nib_trie`). `nibble_trie`
58 /// itself uses `0` as its sentinel (see `OptNz`), but keeps this method so
59 /// the trait stays shared.
60 fn max_value_sentinel() -> Self;
61 /// Convert from `usize`. May panic or truncate on overflow in debug builds.
62 fn from_usize(n: usize) -> Self;
63 /// Compute a 16-bit occupancy mask from a 16-slot children array.
64 /// Bit N is set if `children[N]` is not zero.
65 fn children_mask(children: &[Self; 16]) -> u16;
66}
67
68impl TrieIndex for u8 {
69 #[inline] fn as_usize(self) -> usize { self as usize }
70 #[inline] fn max_value() -> usize { u8::MAX as usize }
71 #[inline] fn zero() -> Self { 0 }
72 #[inline] fn max_value_sentinel() -> Self { u8::MAX }
73 #[inline] fn from_usize(n: usize) -> Self {
74 debug_assert!(n <= u8::MAX as usize, "u8 overflow: {n}");
75 n as u8
76 }
77 #[inline] fn children_mask(children: &[Self; 16]) -> u16 {
78 crate::simd::children_mask_u8(children)
79 }
80}
81
82impl TrieIndex for u16 {
83 #[inline] fn as_usize(self) -> usize { self as usize }
84 #[inline] fn max_value() -> usize { u16::MAX as usize }
85 #[inline] fn zero() -> Self { 0 }
86 #[inline] fn max_value_sentinel() -> Self { u16::MAX }
87 #[inline] fn from_usize(n: usize) -> Self {
88 debug_assert!(n <= u16::MAX as usize, "u16 overflow: {n}");
89 n as u16
90 }
91 #[inline] fn children_mask(children: &[Self; 16]) -> u16 {
92 crate::simd::children_mask_u16(children)
93 }
94}
95
96impl TrieIndex for u32 {
97 #[inline] fn as_usize(self) -> usize { self as usize }
98 #[inline] fn max_value() -> usize { u32::MAX as usize }
99 #[inline] fn zero() -> Self { 0 }
100 #[inline] fn max_value_sentinel() -> Self { u32::MAX }
101 #[inline] fn from_usize(n: usize) -> Self {
102 debug_assert!(n <= u32::MAX as usize, "u32 overflow: {n}");
103 n as u32
104 }
105 #[inline] fn children_mask(children: &[Self; 16]) -> u16 {
106 crate::simd::children_mask(children)
107 }
108}
109
110impl TrieIndex for u64 {
111 #[inline] fn as_usize(self) -> usize { self as usize }
112 #[inline] fn max_value() -> usize { u64::MAX as usize }
113 #[inline] fn zero() -> Self { 0 }
114 #[inline] fn max_value_sentinel() -> Self { u64::MAX }
115 #[inline] fn from_usize(n: usize) -> Self { n as u64 }
116 #[inline] fn children_mask(children: &[Self; 16]) -> u16 {
117 crate::simd::children_mask_u64(children)
118 }
119}
120
121// ---------------------------------------------------------------------------
122// OptNz: 0-encoded optional index (no tag byte, layout-identical to PTR)
123// ---------------------------------------------------------------------------
124
125/// A nonzero-style optional index: a `#[repr(transparent)]` wrapper over `PTR`
126/// where the value `0` denotes "empty" and any nonzero value is a real index.
127///
128/// `OptNz<PTR>` has the same size and layout as `PTR`, so `[OptNz<PTR>; 16]` is
129/// layout-identical to `[PTR; 16]` (used to feed the SIMD `children_mask`). This
130/// is the stable, no-`unsafe`-on-access equivalent of `Option<NonZero<PTR>>`.
131#[repr(transparent)]
132#[derive(Copy, Clone, PartialEq, Eq)]
133pub(crate) struct OptNz<PTR: TrieIndex>(PTR);
134
135impl<PTR: TrieIndex> OptNz<PTR> {
136 /// The empty value (encodes `0`).
137 #[inline]
138 pub(crate) fn empty() -> Self { Self(PTR::zero()) }
139
140 /// Build from a raw `PTR`. Returns `None` if `v` is zero.
141 #[allow(dead_code)]
142 #[inline]
143 pub(crate) fn new(v: PTR) -> Option<Self> {
144 if v == PTR::zero() { None } else { Some(Self(v)) }
145 }
146
147 /// Build from a known-nonzero `PTR`. Debug-asserts `v != 0`.
148 #[inline]
149 pub(crate) fn from_index(v: PTR) -> Self {
150 debug_assert!(v != PTR::zero(), "OptNz::from_index: zero value");
151 Self(v)
152 }
153
154 /// The raw underlying `PTR` (zero if empty).
155 #[inline]
156 pub(crate) fn get(self) -> PTR { self.0 }
157
158 /// Whether this slot holds a real index.
159 #[inline]
160 pub(crate) fn is_some(self) -> bool { self.0 != PTR::zero() }
161
162 /// Whether this slot is empty.
163 #[inline]
164 pub(crate) fn is_none(self) -> bool { self.0 == PTR::zero() }
165}
166
167impl<PTR: TrieIndex> Default for OptNz<PTR> {
168 fn default() -> Self { Self(PTR::zero()) }
169}
170
171impl<PTR: TrieIndex> fmt::Debug for OptNz<PTR> {
172 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
173 if self.is_none() { write!(f, "-") } else { write!(f, "{:?}", self.0) }
174 }
175}
176
177// ---------------------------------------------------------------------------
178// Core types
179// ---------------------------------------------------------------------------
180
181/// A single node in the nibble trie arena.
182///
183/// Generic over `PTR` (pointer/index type for children and arena references)
184/// and `LEN` (length type for prefix lengths and key lengths).
185///
186/// Layout with PTR=u32, LEN=u16: 76 bytes (64 children + 2 prefix_len + 2
187/// leaf_mask + 4 leaf + 1 terminal + 3 padding).
188/// With PTR=u16, LEN=u16: 40 bytes (32 children + 2 + 2 + 2 + 1 + 1 padding).
189#[derive(Copy, Clone)]
190pub(crate) struct Node<PTR: TrieIndex, LEN: TrieIndex> {
191 pub(crate) children: [OptNz<PTR>; 16], // 0 = empty; leaf key index or arena index otherwise
192 pub(crate) prefix_len: LEN, // absolute nibble position of the discriminating nibble
193 pub(crate) leaf_mask: u16, // bit N set → children[N] is a leaf key index
194 pub(crate) leaf: OptNz<PTR>, // key index of a reference/descendant leaf (for retrieval)
195 pub(crate) terminal: bool, // true → this node's key ends here (prefix key)
196}
197
198impl<PTR: TrieIndex, LEN: TrieIndex> Node<PTR, LEN> {
199 pub(crate) fn new() -> Self {
200 Node {
201 children: [OptNz::empty(); 16],
202 prefix_len: LEN::zero(),
203 leaf_mask: 0,
204 leaf: OptNz::empty(),
205 terminal: false,
206 }
207 }
208
209 /// Whether this node is terminal (its own key ends here).
210 #[inline]
211 pub(crate) fn is_terminal(&self) -> bool {
212 self.terminal
213 }
214
215 /// Set the terminal flag.
216 #[inline]
217 fn set_terminal(&mut self, val: bool) {
218 self.terminal = val;
219 }
220
221 /// Check if nibble slot `nib` is a leaf (key index).
222 #[inline]
223 pub(crate) fn is_leaf(&self, nib: usize) -> bool {
224 debug_assert!(nib < 16);
225 (self.leaf_mask >> nib) & 1 == 1
226 }
227
228 /// Set the leaf flag for nibble slot `nib`.
229 #[inline]
230 fn set_leaf(&mut self, nib: usize) {
231 debug_assert!(nib < 16);
232 self.leaf_mask |= 1 << nib;
233 }
234
235 /// Clear the leaf flag for nibble slot `nib`.
236 #[inline]
237 fn clear_leaf(&mut self, nib: usize) {
238 debug_assert!(nib < 16);
239 self.leaf_mask &= !(1 << nib);
240 }
241
242 /// Check if nibble slot `nib` is occupied (holds a child, leaf or internal).
243 #[inline]
244 pub(crate) fn is_occupied(&self, nib: usize) -> bool {
245 debug_assert!(nib < 16);
246 self.children[nib].is_some()
247 }
248
249 /// Store a leaf key index at `nib`. Key index must be nonzero.
250 #[inline]
251 fn set_leaf_child(&mut self, nib: usize, key_index: PTR) {
252 debug_assert!(nib < 16);
253 debug_assert!(key_index != PTR::zero(), "zero key index");
254 self.set_leaf(nib);
255 self.children[nib] = OptNz::from_index(key_index);
256 }
257
258 /// Store an arena index at `nib` (internal node reference). Must be nonzero.
259 #[inline]
260 fn set_internal_child(&mut self, nib: usize, arena_idx: PTR) {
261 debug_assert!(nib < 16);
262 debug_assert!(arena_idx != PTR::zero(), "zero arena index");
263 self.clear_leaf(nib);
264 self.children[nib] = OptNz::from_index(arena_idx);
265 }
266
267 /// Decode a leaf child at `nib` into a key index.
268 /// Returns `None` if the slot is empty or not a leaf.
269 #[inline]
270 fn leaf_key_index(&self, nib: usize) -> Option<PTR> {
271 debug_assert!(nib < 16);
272 if self.is_leaf(nib) && self.children[nib].is_some() {
273 Some(self.children[nib].get())
274 } else {
275 None
276 }
277 }
278
279 /// Compute a 16-bit mask where bit N is set if `children[N]` is occupied.
280 /// Reuses the SIMD `children_mask` over the raw `[PTR; 16]` view — sound
281 /// because `OptNz<PTR>` is `#[repr(transparent)]` over `PTR`.
282 #[inline]
283 pub(crate) fn children_mask(&self) -> u16 {
284 // SAFETY: OptNz<PTR> is #[repr(transparent)] over PTR, so
285 // [OptNz<PTR>; 16] has identical layout to [PTR; 16].
286 let raw: &[PTR; 16] = unsafe { &*(&self.children as *const [OptNz<PTR>; 16] as *const [PTR; 16]) };
287 PTR::children_mask(raw)
288 }
289
290 /// Promote this node's PTR type to a wider one.
291 /// Child arena indices and leaf key indices are widened via `NewPTR::from_usize`.
292 pub(crate) fn promote<NewPTR: TrieIndex>(self) -> Node<NewPTR, LEN> {
293 let mut children = [OptNz::empty(); 16];
294 for i in 0..16 {
295 if self.children[i].is_some() {
296 children[i] = OptNz::from_index(NewPTR::from_usize(self.children[i].get().as_usize()));
297 }
298 }
299 Node {
300 children,
301 prefix_len: self.prefix_len,
302 leaf_mask: self.leaf_mask,
303 leaf: if self.leaf.is_some() {
304 OptNz::from_index(NewPTR::from_usize(self.leaf.get().as_usize()))
305 } else {
306 OptNz::empty()
307 },
308 terminal: self.terminal,
309 }
310 }
311
312 /// Demote this node's PTR type to a narrower one.
313 /// Returns `Err(self)` if any child index or leaf index doesn't fit
314 /// in the narrower type.
315 pub(crate) fn demote<NewPTR: TrieIndex>(self) -> Result<Node<NewPTR, LEN>, Self> {
316 for i in 0..16 {
317 if self.children[i].is_some() && self.children[i].get().as_usize() > NewPTR::max_value() {
318 return Err(self);
319 }
320 }
321 if self.leaf.is_some() && self.leaf.get().as_usize() > NewPTR::max_value() {
322 return Err(self);
323 }
324 let mut children = [OptNz::empty(); 16];
325 for i in 0..16 {
326 if self.children[i].is_some() {
327 children[i] = OptNz::from_index(NewPTR::from_usize(self.children[i].get().as_usize()));
328 }
329 }
330 Ok(Node {
331 children,
332 prefix_len: self.prefix_len,
333 leaf_mask: self.leaf_mask,
334 leaf: if self.leaf.is_some() {
335 OptNz::from_index(NewPTR::from_usize(self.leaf.get().as_usize()))
336 } else {
337 OptNz::empty()
338 },
339 terminal: self.terminal,
340 })
341 }
342}
343
344impl<PTR: TrieIndex, LEN: TrieIndex> fmt::Debug for Node<PTR, LEN> {
345 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
346 let active: Vec<(usize, &str, PTR)> = (0..16)
347 .filter(|&n| self.children[n].is_some())
348 .map(|n| {
349 let tag = if self.is_leaf(n) { "L" } else { "I" };
350 (n, tag, self.children[n].get())
351 })
352 .collect();
353 f.debug_struct("Node")
354 .field("prefix_len", &self.prefix_len)
355 .field("leaf_mask", &format_args!("{:016b}", self.leaf_mask))
356 .field("terminal", &self.terminal)
357 .field("leaf", &self.leaf)
358 .field("children", &active)
359 .finish()
360 }
361}
362
363// ---------------------------------------------------------------------------
364// FlatNode (Fnode) — dense leaf-pack node (step 4: base + terminal + offset)
365// ---------------------------------------------------------------------------
366
367/// Maximum number of keys a [`FlatNode`] can hold: 1 reference key (`base`) +
368/// `FNODE_SLOTS` array slots.
369pub(crate) const FNODE_CAP: usize = 16;
370
371/// Number of array slots in a [`FlatNode`] (one less than [`FNODE_CAP`] — the
372/// leftmost/reference key is pulled out of the array into `base`).
373pub(crate) const FNODE_SLOTS: usize = 15;
374
375/// `offset` value meaning "branch marker" (no terminal key at this slot; its
376/// children follow as deeper array slots). Real offsets are `>= 1` because
377/// `base` is the smallest key index in the subtree.
378pub(crate) const FNODE_OFFSET_NULL: u8 = 0xFF;
379
380/// A dense leaf-pack node: collapses a small/deep subtree (≤ [`FNODE_CAP`]
381/// keys) into one node holding a flattened pre-order micro-trie.
382///
383/// **Encoding (step 4, revised):** `index` is kept in sorted key order (insert
384/// places each key at its sorted position), so a subtree's keys appear in
385/// `index` in increasing position order. The leftmost (reference) key's absolute
386/// `index` position is stored once as `base`; every other key is stored as a
387/// `u8` **offset** from `base` (`key_index = base + offset`). This collapses the
388/// 4 B ptr to 1 B and pays for the larger CAP. Keys still live in `buf`, pointed
389/// to by `index` — no change to key storage.
390///
391/// - `base` — the leftmost key's `index` position. Doubles as the reference key
392/// (same role as `Inode.leaf`) for `simd_check_prefix`. Its discriminating
393/// depth is `parent.prefix_len` (the parent already matched the edge nibble
394/// there), so it is **not stored** and **not an array slot**.
395/// - `terminal` — whether `base` (the subtree root) is itself terminal. `true` +
396/// deeper slots = terminal+branch root; `true` + no deeper slots = pure-leaf
397/// root; `false` = pure-branch root (`base` is reference-only). This lifts the
398/// step-3 "subtree root can't be terminal" restriction — the root gets its own
399/// representation outside the slot array.
400/// - `slots` — the non-leftmost keys, each `(prefix_len, offset)`: `prefix_len`
401/// is the discriminating depth (absolute nibble position where this key
402/// diverges from a sibling); `offset` is `key_index - base`. `offset ==
403/// [`FNODE_OFFSET_NULL`]` → pure branch marker (its children follow as deeper
404/// slots); otherwise a terminal key at `base + offset`. Because `base` is the
405/// smallest index in the subtree, real offsets are `>= 1`.
406///
407/// A `Some`-offset (≠ NULL) slot with deeper slots following is a
408/// **terminal+branch** node (a prefix key); `flat_get` descends past it when the
409/// query continues (`can_descend`) and lands on it (returning the terminal,
410/// verified by `simd_eq`) when the query is exhausted. A NULL-offset slot is a
411/// pure (non-terminal) branch. An Fnode is a DAG leaf — slots hold only key
412/// indices, never arena refs; multi-level structure is encoded via pre-order
413/// `prefix_len` (the flat scan algorithm).
414///
415/// `u8` offsets are safe because NibbleTrie is **insert-only** (no `remove`):
416/// `index` density stays 50–90% (`optimize`'s `2i+1` respread + the `>90%`
417/// trigger), so a ≤16-key subtree spans ≤~32 `index` slots → offsets ≤~32 ≪
418/// `0xFF`. No flatten guard needed now (if deletion is ever added: a `span ≤ 254`
419/// flatten-guard + split-on-overflow trigger).
420///
421/// `FlatNode` is `Copy`: every field (and every `TinyArray` element) is `Copy`,
422/// and `TinyArray` itself is `Copy` (no heap allocation, no `Drop`). So
423/// [`ArenaNode`] is `Copy` too — no borrow-not-copy constraint on the arena.
424#[derive(Copy, Clone, Debug)]
425pub(crate) struct FlatNode<PTR: TrieIndex, LEN: TrieIndex> {
426 pub(crate) nibbles: u64, // 15 nibbles × 4 bits (array slots 0..FNODE_SLOTS)
427 pub(crate) base: PTR, // index into `index` of the leftmost (reference) key
428 pub(crate) terminal: bool, // whether `base` (the subtree root) is itself terminal
429 pub(crate) slots: TinyArray<(LEN, u8), FNODE_SLOTS>, // (prefix_len, offset); offset 0xFF = branch marker
430}
431
432impl<PTR: TrieIndex, LEN: TrieIndex> FlatNode<PTR, LEN> {
433 pub(crate) fn new() -> Self {
434 FlatNode {
435 nibbles: 0,
436 base: PTR::zero(),
437 terminal: false,
438 slots: TinyArray::new(),
439 }
440 }
441
442 /// The `index` position of the key at array slot `i` (`base + offset`), or
443 /// `None` if slot `i` is a branch marker (offset == [`FNODE_OFFSET_NULL`]).
444 #[allow(dead_code)]
445 #[inline]
446 pub(crate) fn slot_key_index(&self, i: usize) -> Option<PTR> {
447 let (_plen, offset) = self.slots.as_slice()[i];
448 if offset == FNODE_OFFSET_NULL {
449 None
450 } else {
451 Some(PTR::from_usize(self.base.as_usize() + offset as usize))
452 }
453 }
454
455 /// The nibble stored at array slot `i`.
456 #[inline]
457 pub(crate) fn slot_nibble(&self, i: usize) -> u8 {
458 ((self.nibbles >> (4 * i)) & 0xF) as u8
459 }
460
461 /// The key index at [`Frame::Fnode`] position `pos`: `0` = `base`, `i+1` =
462 /// array slot `i`. Returns `None` if `pos` is not a terminal — `base` when
463 /// `!terminal`, or an array branch-marker slot (the latter never occurs: the
464 /// iterator only ever positions on terminals). Pre-order (base, then array
465 /// slots in nibble order) is sorted key order, so `pos` enumerates terminals
466 /// in ascending key order.
467 #[inline]
468 pub(crate) fn pos_key_index(&self, pos: usize) -> Option<PTR> {
469 if pos == 0 {
470 if self.terminal { Some(self.base) } else { None }
471 } else {
472 let i = pos - 1;
473 let (_plen, offset) = self.slots.as_slice()[i];
474 if offset == FNODE_OFFSET_NULL {
475 None
476 } else {
477 Some(PTR::from_usize(self.base.as_usize() + offset as usize))
478 }
479 }
480 }
481
482 /// First terminal position: `0` if `terminal`, else the first array slot
483 /// with a non-NULL offset (encoded as `slot+1`). `None` if no terminals.
484 #[inline]
485 pub(crate) fn first_terminal_pos(&self) -> Option<usize> {
486 if self.terminal {
487 Some(0)
488 } else {
489 self.next_terminal_pos(0)
490 }
491 }
492
493 /// Next terminal position strictly after `pos`: scans array slots from
494 /// `pos` for the next non-NULL offset (returned as `slot+1`). `pos==0`
495 /// (after `base`) starts at array slot 0; `pos==i+1` (after array slot `i`)
496 /// starts at array slot `i+1`. `None` if exhausted (caller pops the frame).
497 #[inline]
498 pub(crate) fn next_terminal_pos(&self, pos: usize) -> Option<usize> {
499 let slots = self.slots.as_slice();
500 for i in pos..slots.len() {
501 let (_plen, offset) = slots[i];
502 if offset != FNODE_OFFSET_NULL {
503 return Some(i + 1);
504 }
505 }
506 None
507 }
508
509 /// Number of terminal keys this Fnode represents: `base` (if `terminal`) plus
510 /// every array slot with a non-NULL offset. When `terminal=false`, `base` is
511 /// itself an array slot (offset 0), so it is counted by the loop; when `true`,
512 /// `base` is pulled out of the array and counted here.
513 pub(crate) fn key_count(&self) -> usize {
514 let mut n = if self.terminal { 1 } else { 0 };
515 for (_, offset) in self.slots.as_slice() {
516 if *offset != FNODE_OFFSET_NULL {
517 n += 1;
518 }
519 }
520 n
521 }
522
523 /// Promote the reference key index type to a wider `PTR` (only `base`
524 /// carries a `PTR`; the array slots are `(LEN, u8)` offsets).
525 fn promote<NewPTR: TrieIndex>(self) -> FlatNode<NewPTR, LEN> {
526 FlatNode {
527 nibbles: self.nibbles,
528 base: NewPTR::from_usize(self.base.as_usize()),
529 terminal: self.terminal,
530 slots: self.slots,
531 }
532 }
533
534 /// Demote the reference key index type to a narrower `PTR`. Returns
535 /// `Err(self)` if `base` doesn't fit in the narrower type. (Array-slot
536 /// offsets are `u8`, so they always fit.)
537 fn demote<NewPTR: TrieIndex>(self) -> Result<FlatNode<NewPTR, LEN>, Self> {
538 if self.base.as_usize() > NewPTR::max_value() {
539 return Err(self);
540 }
541 Ok(FlatNode {
542 nibbles: self.nibbles,
543 base: NewPTR::from_usize(self.base.as_usize()),
544 terminal: self.terminal,
545 slots: self.slots,
546 })
547 }
548}
549
550impl<PTR: TrieIndex, LEN: TrieIndex> Default for FlatNode<PTR, LEN> {
551 fn default() -> Self { Self::new() }
552}
553
554/// Tagged arena element: `Inode` (the existing 16-slot direct-addressed
555/// [`Node`]) or `Fnode` (a [`FlatNode`]). `Copy` — both variants are `Copy`
556/// (`FlatNode` is `Copy` since `TinyArray` is), so arena reads may copy freely.
557#[derive(Copy, Clone, Debug)]
558pub(crate) enum ArenaNode<PTR: TrieIndex, LEN: TrieIndex> {
559 Inode(Node<PTR, LEN>),
560 Fnode(FlatNode<PTR, LEN>),
561}
562
563impl<PTR: TrieIndex, LEN: TrieIndex> ArenaNode<PTR, LEN> {
564 /// Promote the arena index type to a wider `PTR` (dispatches by variant).
565 fn promote<NewPTR: TrieIndex>(self) -> ArenaNode<NewPTR, LEN> {
566 match self {
567 ArenaNode::Inode(n) => ArenaNode::Inode(n.promote()),
568 ArenaNode::Fnode(f) => ArenaNode::Fnode(f.promote()),
569 }
570 }
571
572 /// Demote the arena index type to a narrower `PTR` (dispatches by variant).
573 /// Returns `Err(self)` if any index doesn't fit.
574 fn demote<NewPTR: TrieIndex>(self) -> Result<ArenaNode<NewPTR, LEN>, Self> {
575 match self {
576 ArenaNode::Inode(n) => n.demote().map(ArenaNode::Inode).map_err(ArenaNode::Inode),
577 ArenaNode::Fnode(f) => f.demote().map(ArenaNode::Fnode).map_err(ArenaNode::Fnode),
578 }
579 }
580}
581
582// ---------------------------------------------------------------------------
583// NibbleTrie
584// ---------------------------------------------------------------------------
585
586#[derive(Clone)]
587pub struct NibbleTrie<K, T, PTR: TrieIndex = u32, LEN: TrieIndex = u16>
588where
589 K: ByteKey,
590{
591 pub(crate) arena: Vec<ArenaNode<PTR, LEN>>,
592 pub(crate) buf: Vec<u8>, // all keys concatenated (no null terminators)
593 pub(crate) index: Vec<Option<Slot<LEN, T>>>, // sparse: position == key index; None = gap; [0] = dummy
594 pub(crate) n_keys: usize, // live key count (replaces index.len()-1)
595 _key: PhantomData<K>,
596}
597
598// ---------------------------------------------------------------------------
599// Divergence result
600// ---------------------------------------------------------------------------
601
602/// Outcome of comparing two keys for divergence starting from a given nibble
603/// position. `from` lets callers skip already-confirmed-matching prefixes.
604enum DivergeResult {
605 /// The keys are identical (same nibble count, same content).
606 Duplicate,
607 /// The keys diverge at this nibble position, or one key is a prefix of the
608 /// other (position = length of the shorter key in nibbles).
609 At(usize),
610}
611
612/// Outcome of a bounded prefix check: scan nibbles `from..to` and report
613/// whether the keys match in that range or diverge at a specific nibble.
614/// Unlike `DivergeResult`, this does not scan past `to` and has no
615/// `Duplicate` variant — a full match within the bound is `Matches`.
616enum PrefixCheck {
617 /// The keys match at every nibble position in `from..to`.
618 Matches,
619 /// The keys diverge at this nibble position (within `from..to`).
620 Diverges(usize),
621}
622
623/// Scan two keys from `from` onward to find the first diverging nibble.
624#[inline]
625fn find_divergence(key_a: &[u8], key_b: &[u8], from: usize) -> DivergeResult {
626 let total_a = nibble_count(key_a);
627 let total_b = nibble_count(key_b);
628 let min = total_a.min(total_b);
629 let mut d = from;
630 while d < min {
631 if key_nibble_at(key_a, d) != key_nibble_at(key_b, d) {
632 return DivergeResult::At(d);
633 }
634 d += 1;
635 }
636 if total_a == total_b {
637 DivergeResult::Duplicate
638 } else {
639 DivergeResult::At(d)
640 }
641}
642
643/// Given two differing bytes, return the nibble index of the first divergence.
644/// High nibble (bits 7–4) is checked first; if they match, the low nibble
645/// (bits 3–0) diverges. Branchless: XOR → check if high nibble is zero → add 1.
646#[inline]
647fn diverging_nibble(xor: u8, byte_idx: usize) -> usize {
648 byte_idx * 2 + ((xor >> 4 == 0) as usize)
649}
650
651/// SIMD-accelerated byte equality check. Returns `true` if both slices have
652/// the same length and identical content. Uses 16-byte lanes for the bulk
653/// of the comparison, with a scalar tail for the remainder.
654#[inline]
655fn simd_eq(a: &[u8], b: &[u8]) -> bool {
656 if a.len() != b.len() {
657 return false;
658 }
659 let len = a.len();
660 let mut i = 0;
661 while i + 16 <= len {
662 let va = Simd::<u8, 16>::from_slice(unsafe { a.get_unchecked(i..i + 16) });
663 let vb = Simd::<u8, 16>::from_slice(unsafe { b.get_unchecked(i..i + 16) });
664 if va.simd_ne(vb).any() {
665 return false;
666 }
667 i += 16;
668 }
669 // Scalar tail
670 while i < len {
671 if unsafe { *a.get_unchecked(i) != *b.get_unchecked(i) } {
672 return false;
673 }
674 i += 1;
675 }
676 true
677}
678
679fn simd_find_divergence<const N: usize>(key_a: &[u8], key_b: &[u8], from: usize) -> DivergeResult
680{
681 let minlen = key_a.len().min(key_b.len());
682 let mut i = from / 2; // byte containing nibble `from`
683
684 while i + N <= minlen {
685 let a = Simd::<u8, N>::from_slice(unsafe { key_a.get_unchecked(i..i + N) });
686 let b = Simd::<u8, N>::from_slice(unsafe { key_b.get_unchecked(i..i + N) });
687 let mask = a.simd_ne(b);
688 if mask.any() {
689 let diff_byte_idx = i + mask.first_set().unwrap();
690 let xor = unsafe { *key_a.get_unchecked(diff_byte_idx) ^ *key_b.get_unchecked(diff_byte_idx) };
691 return DivergeResult::At(diverging_nibble(xor, diff_byte_idx));
692 }
693 i += N;
694 }
695
696 // Scalar tail
697 find_divergence(key_a, key_b, i * 2)
698}
699
700/// Scan nibbles `from..to` of two keys. Returns `Diverges(pos)` if they differ
701/// at any nibble in that range, or `Matches` if they agree throughout.
702/// An empty range (`from >= to`) is trivially `Matches`.
703#[inline]
704fn check_prefix(key_a: &[u8], key_b: &[u8], from: usize, to: usize) -> PrefixCheck {
705 for nib in from..to {
706 if key_nibble_at(key_a, nib) != key_nibble_at(key_b, nib) {
707 return PrefixCheck::Diverges(nib);
708 }
709 }
710 PrefixCheck::Matches
711}
712
713/// SIMD-accelerated bounded prefix check. Scans nibbles `from..to` and stops
714/// at the first divergence within that range. Returns `Matches` if the keys
715/// agree throughout, or `Diverges(pos)` at the first differing nibble.
716fn simd_check_prefix<const N: usize>(key_a: &[u8], key_b: &[u8], from: usize, to: usize) -> PrefixCheck
717{
718 if from >= to {
719 return PrefixCheck::Matches;
720 }
721
722 let from_byte = from / 2;
723 let to_byte = (to + 1) / 2; // first byte fully outside the nibble range
724 let minlen = key_a.len().min(key_b.len()).min(to_byte);
725 let mut i = from_byte;
726
727 while i + N <= minlen {
728 let a = Simd::<u8, N>::from_slice(unsafe { key_a.get_unchecked(i..i + N) });
729 let b = Simd::<u8, N>::from_slice(unsafe { key_b.get_unchecked(i..i + N) });
730 let mask = a.simd_ne(b);
731 if mask.any() {
732 let diff_byte_idx = i + mask.first_set().unwrap();
733 let xor = unsafe { *key_a.get_unchecked(diff_byte_idx) ^ *key_b.get_unchecked(diff_byte_idx) };
734 let nib = diverging_nibble(xor, diff_byte_idx);
735 if nib < to {
736 return PrefixCheck::Diverges(nib);
737 }
738 // Divergence past the bound — keys match within range
739 return PrefixCheck::Matches;
740 }
741 i += N;
742 }
743
744 // Scalar tail
745 check_prefix(key_a, key_b, i * 2, to)
746}
747
748// ---------------------------------------------------------------------------
749// Nibble helpers
750// ---------------------------------------------------------------------------
751
752#[inline]
753fn key_nibble_at(key: &[u8], idx: usize) -> u8 {
754 let byte_idx = idx / 2;
755 if byte_idx < key.len() {
756 if idx % 2 == 0 {
757 key[byte_idx] >> 4
758 } else {
759 key[byte_idx] & 0x0F
760 }
761 } else {
762 0
763 }
764}
765
766/// Unchecked version of `key_nibble_at`.
767///
768/// # Safety
769/// `idx / 2` must be < `key.len()` (i.e., the nibble index must be in bounds).
770#[allow(dead_code)]
771#[inline]
772unsafe fn key_nibble_at_unchecked(key: &[u8], idx: usize) -> u8 {
773 let byte_idx = idx / 2;
774 debug_assert!(byte_idx < key.len(), "nibble {idx} out of bounds for key len {}", key.len());
775 if idx % 2 == 0 {
776 unsafe { *key.get_unchecked(byte_idx) >> 4 }
777 } else {
778 unsafe { *key.get_unchecked(byte_idx) & 0x0F }
779 }
780}
781
782#[inline]
783fn nibble_count(key: &[u8]) -> usize {
784 key.len() * 2
785}
786
787// ---------------------------------------------------------------------------
788// NibbleTrie methods
789// ---------------------------------------------------------------------------
790
791impl<K: ByteKey, T, PTR: TrieIndex, LEN: TrieIndex> NibbleTrie<K, T, PTR, LEN> {
792 /// Return the key slice for `key_index`.
793 #[inline]
794 fn key_slice(&self, key_index: PTR) -> &[u8] {
795 let (off, len, _) = self.index[key_index.as_usize()].as_ref().unwrap();
796 &self.buf[off.get()..off.get() + len.as_usize()]
797 }
798
799 /// Borrow the `Inode` at arena index `i`.
800 ///
801 /// This is the single chokepoint for the **Inode-only** code paths (insert,
802 /// bump_walk, optimize, the invariant oracle) that do not yet handle Fnodes.
803 /// The read path (`get`/`get_unchecked`) and `NibbleIter` dispatch Fnodes
804 /// separately (`flat_get` / `Frame::Fnode`) and never call this on an Fnode.
805 /// Panics if `arena[i]` is an `Fnode` — a sign an Inode-only path reached one
806 /// (step 4/5 wire those paths).
807 #[inline]
808 fn inode(&self, i: usize) -> &Node<PTR, LEN> {
809 match &self.arena[i] {
810 ArenaNode::Inode(n) => n,
811 ArenaNode::Fnode(_) => panic!("inode(): arena[{i}] is an Fnode (Inode-only path)"),
812 }
813 }
814
815 /// Mutably borrow the `Inode` at arena index `i`. See [`inode`](Self::inode).
816 #[inline]
817 fn inode_mut(&mut self, i: usize) -> &mut Node<PTR, LEN> {
818 match &mut self.arena[i] {
819 ArenaNode::Inode(n) => n,
820 ArenaNode::Fnode(_) => panic!("inode_mut(): arena[{i}] is an Fnode (Inode-only path)"),
821 }
822 }
823
824 pub fn new() -> Self {
825 NibbleTrie {
826 arena: Vec::new(),
827 buf: vec![0], // buf[0] = dummy (unused byte)
828 index: vec![None], // index[0] = dummy gap
829 n_keys: 0,
830 _key: PhantomData,
831 }
832 }
833
834 pub fn len(&self) -> usize {
835 self.n_keys
836 }
837
838 pub fn is_empty(&self) -> bool {
839 self.n_keys == 0
840 }
841
842 // -----------------------------------------------------------------------
843 // Lookup
844 // -----------------------------------------------------------------------
845
846 /// Flat scan over a [`FlatNode`] (Fnode): a pre-order DFS of a
847 /// path-compressed micro-trie. The Fnode collapses the subtree **rooted at
848 /// `base`** (the leftmost key), so every array slot is a *descendant* of
849 /// `base` and diverges at a depth `> parent.prefix_len` (= `P`, the depth
850 /// the parent Inode already matched when it dispatched here). `base`'s own
851 /// discriminating depth is `P` — already consumed — so it is not stored and
852 /// not an array slot; the scan walks only the array slots (all depth `> P`)
853 /// and falls back to `base` when no array slot is reachable.
854 ///
855 /// Each array slot `i` is `(prefix_len, offset)`: `prefix_len` is the
856 /// *discriminating depth* (absolute nibble position where this key diverges
857 /// from a sibling); `offset` is `key_index - base` (`0xFF` = branch marker,
858 /// no terminal; otherwise a terminal key index at `base + offset`).
859 ///
860 /// A non-NULL offset with deeper slots following is a **terminal+branch**
861 /// node (a prefix key). The scan handles terminals and branches uniformly:
862 /// on a nibble match, descend into the entry's subtree iff the next entry is
863 /// strictly deeper *and* the query hasn't exhausted (`can_descend`);
864 /// otherwise *land* on this slot — return the terminal (verified by full-key
865 /// `simd_eq`, since path compression means bytes between discriminant depths
866 /// were never compared) for a non-NULL offset, or `None` for a branch
867 /// marker. A terminal+branch slot is **descended past** when the query
868 /// continues (the longer key lives below) and **landed on** when the query
869 /// is exhausted (the prefix key itself).
870 ///
871 /// When the scan exhausts / surfaces above the frontier without landing on
872 /// an array slot, the query's path leads to `base`: return `base` iff
873 /// `terminal` and `simd_eq(base_key, query)`. (`base` at depth `P` is the
874 /// only terminal not encoded as an array slot; the parent already matched
875 /// its nibble at `P`, so it is the implicit landing point for a query that
876 /// equals `base` or is a prefix of all array keys.)
877 ///
878 /// Algorithm mirrors `notes/fnode.md` §"Step 4 design (REVISED)" / §"flat
879 /// scan algorithm": descend following the query key's nibbles; on a nibble
880 /// mismatch advance — entries in a subtree we haven't descended into
881 /// (`d > depth`) are skipped by the depth guard.
882 fn flat_get(&self, node: &FlatNode<PTR, LEN>, key: &[u8]) -> Option<usize> {
883 let slots = node.slots.as_slice();
884 let max_nib = key.len() * 2;
885 // Scan the array slots (all depth > P). Skip the scan entirely if the
886 // shallowest array slot is already past the query's length — then the
887 // query can only land on `base`.
888 if !slots.is_empty() {
889 let mut depth = slots[0].0.as_usize(); // shallowest array-slot depth
890 if depth < max_nib {
891 let mut i = 0;
892 while i < slots.len() {
893 let d = slots[i].0.as_usize();
894 if d < depth {
895 // Surfaced above the current frontier — no further match.
896 break;
897 }
898 if d > depth {
899 // In a subtree we haven't descended into — skip.
900 i += 1;
901 continue;
902 }
903 let nib = node.slot_nibble(i);
904 if key_nibble_at(key, d) != nib {
905 i += 1;
906 continue;
907 }
908 // On path. Can the query descend further into this entry's subtree?
909 let can_descend = i + 1 < slots.len()
910 && slots[i + 1].0.as_usize() > d
911 && slots[i + 1].0.as_usize() < max_nib;
912 if can_descend {
913 depth = slots[i + 1].0.as_usize();
914 i += 1;
915 } else {
916 // Landed on array slot i — the query can't go deeper.
917 // The offset tells terminal-ness; a non-NULL offset is
918 // verified by full-key equality (path compression).
919 let offset = slots[i].1;
920 return if offset != FNODE_OFFSET_NULL {
921 let ki = node.base.as_usize() + offset as usize;
922 if simd_eq(self.key_slice(PTR::from_usize(ki)), key) {
923 Some(ki)
924 } else {
925 None
926 }
927 } else {
928 None // pure branch marker
929 };
930 }
931 }
932 }
933 }
934 // No array slot matched at the query's remaining depth → land on `base`.
935 // Return it iff `terminal` and its full key equals the query.
936 if node.terminal {
937 let ki = node.base;
938 if simd_eq(self.key_slice(ki), key) {
939 return Some(ki.as_usize());
940 }
941 }
942 None
943 }
944
945 pub fn get_index(&self, key: &[u8]) -> Option<usize> {
946 if self.arena.is_empty() {
947 return None;
948 }
949 let mut phys_idx: usize = 0;
950 let max_nib = key.len() * 2;
951 loop {
952 let node = self.inode(phys_idx);
953 let prefix_len = node.prefix_len.as_usize();
954 // Key nibbles exhausted — check if this node is terminal.
955 if prefix_len >= max_nib {
956 if node.is_terminal() {
957 let ki = node.leaf.get();
958 let (off, len, _) = self.index[ki.as_usize()].as_ref().unwrap();
959 let off = off.get();
960 let key_in_buf = &self.buf[off..off + len.as_usize()];
961 if key.len() == len.as_usize() && simd_eq(&key_in_buf[..key.len()], key) {
962 return Some(ki.as_usize());
963 }
964 }
965 return None;
966 }
967 let nib = key_nibble_at(key, prefix_len) as usize;
968 if !node.is_occupied(nib) {
969 return None;
970 }
971 if node.is_leaf(nib) {
972 let key_index = node.children[nib].get();
973 return if simd_eq(self.key_slice(key_index), key) {
974 Some(key_index.as_usize())
975 } else {
976 None
977 };
978 }
979 // Internal child — Inode or Fnode.
980 let child = node.children[nib].get().as_usize();
981 match &self.arena[child] {
982 ArenaNode::Inode(_) => phys_idx = child,
983 ArenaNode::Fnode(f) => return self.flat_get(f, key),
984 }
985 }
986 }
987
988 /// Unchecked lookup — assumes the key is present in the trie.
989 ///
990 /// # Safety
991 /// The key **must** have been inserted into this trie. All child/leaf indices
992 /// encountered during traversal must be valid arena or index entries.
993 #[cfg(feature = "unchecked")]
994 unsafe fn get_index_unchecked(&self, key: &[u8]) -> Option<usize> {
995 if self.arena.is_empty() {
996 return None;
997 }
998 let mut phys_idx: usize = 0;
999 let max_nib = key.len() * 2;
1000 loop {
1001 // SAFETY: phys_idx is the root (always an Inode by invariant) or an
1002 // Inode child arena index. Fnode children are dispatched below
1003 // before re-looping, so this read is always an Inode.
1004 let node = match unsafe { self.arena.get_unchecked(phys_idx) } {
1005 ArenaNode::Inode(n) => n,
1006 ArenaNode::Fnode(_) => panic!("get_unchecked: phys_idx {phys_idx} is an Fnode (dispatcher missed it)"),
1007 };
1008 let prefix_len = node.prefix_len.as_usize();
1009 if prefix_len >= max_nib {
1010 debug_assert!(node.is_terminal(), "get_unchecked: key not in set");
1011 return Some(node.leaf.get().as_usize());
1012 }
1013 let nib = unsafe { key_nibble_at_unchecked(key, prefix_len) } as usize;
1014 let slot = unsafe { node.children.get_unchecked(nib) };
1015 if slot.is_none() {
1016 return None;
1017 }
1018 if node.is_leaf(nib) {
1019 return Some(slot.get().as_usize());
1020 }
1021 let child = slot.get().as_usize();
1022 // Internal child — Inode or Fnode.
1023 // SAFETY: `child` is a valid arena index read from an Inode.
1024 match unsafe { self.arena.get_unchecked(child) } {
1025 ArenaNode::Inode(_) => phys_idx = child,
1026 ArenaNode::Fnode(f) => return self.flat_get(f, key),
1027 }
1028 }
1029 }
1030
1031 pub fn get(&self, key: &[u8]) -> Option<&T> {
1032 self.get_index(key).map(|idx| &self.index[idx].as_ref().unwrap().2)
1033 }
1034
1035 pub fn get_mut(&mut self, key: &[u8]) -> Option<&mut T> {
1036 self.get_index(key).map(|idx| &mut self.index[idx].as_mut().unwrap().2)
1037 }
1038
1039 ///if the key is guaranteed to be in the set, the final comparison can be skipped, improving perf substantially.
1040 #[cfg(feature = "unchecked")]
1041 pub unsafe fn get_unchecked(&self, key: &[u8]) -> Option<&T> {
1042 unsafe {self.get_index_unchecked(key).map(|idx| &self.index[idx].as_ref().unwrap().2) }
1043 }
1044
1045 // -----------------------------------------------------------------------
1046 // Iteration
1047 // -----------------------------------------------------------------------
1048
1049 /// An internal tree-walking cursor, used to position the public `Cursor`
1050 /// (via `seek`) and by `bump_walk` (via `seek` + `stack`).
1051 pub(crate) fn walk_iter(&self) -> NibbleIter<'_, K, T, PTR, LEN> {
1052 NibbleIter::new(self)
1053 }
1054
1055 /// Public forward cursor: parked *before* the first key (so `current()` is
1056 /// `None` and `next()` yields the first key). A linear scan over the sparse
1057 /// `index`, skipping `None` gaps.
1058 pub fn iter(&self) -> Cursor<'_, K, T, PTR, LEN> {
1059 Cursor::new(self)
1060 }
1061
1062 /// Public reverse cursor: parked *on* the last key (`current()` returns it,
1063 /// `prev()` walks backward). Linear scan over `index`.
1064 pub fn iter_last(&self) -> Cursor<'_, K, T, PTR, LEN> {
1065 Cursor::new_last(self)
1066 }
1067
1068 /// Public forward mutable cursor: parked *before* the first key, lending out
1069 /// `&mut T` borrows tied to the cursor (see [`CursorMut`]).
1070 pub fn iter_mut(&mut self) -> CursorMut<'_, K, T, PTR, LEN> {
1071 CursorMut::new(self)
1072 }
1073
1074 /// Public reverse mutable cursor: parked *on* the last key, lending out
1075 /// `&mut T` borrows tied to the cursor (see [`CursorMut`]).
1076 pub fn iter_mut_last(&mut self) -> CursorMut<'_, K, T, PTR, LEN> {
1077 CursorMut::new_last(self)
1078 }
1079
1080 /// Iterate the keys in `bounds` in ascending order — a zero-allocation
1081 /// [`Range`] yielding `(K::Borrowed<'_>, &T)`. Both bounds are resolved by
1082 /// O(keylen) seeks up front; the scan between them is then bounded by slot
1083 /// index (`pos < end_pos`), so no per-element key comparison is needed.
1084 /// Accepts any [`RangeBounds<&[u8]>`]: `start..end`, `start..`, `..end`,
1085 /// `..` (operands are `&[u8]`). The bounds' byte slices are used only during
1086 /// the initial seeks and need not outlive the call.
1087 pub fn range<'q>(&self, bounds: impl RangeBounds<&'q [u8]>) -> Range<'_, K, T, PTR, LEN> {
1088 // `RangeBounds<&'q [u8]>::start_bound` returns `Bound<&'q &'q [u8]>`-ish;
1089 // deref the inner reference to get `Bound<&'q [u8]>` for `Range::new`.
1090 let start = bounds.start_bound().map(|b| *b);
1091 let end = bounds.end_bound().map(|b| *b);
1092 Range::new(self, start, end)
1093 }
1094
1095 /// Like [`range`](Self::range) but with explicit [`Bound`]s — for mixed
1096 /// `Included`/`Excluded` bounds without the `&&[u8]` double-reference the
1097 /// `RangeBounds` tuple form would require.
1098 pub fn range_bounds(
1099 &self,
1100 start: Bound<&[u8]>,
1101 end: Bound<&[u8]>,
1102 ) -> Range<'_, K, T, PTR, LEN> {
1103 Range::new(self, start, end)
1104 }
1105
1106 pub fn into_keys_values(self) -> (Vec<K>, Vec<T>) {
1107 let buf = self.buf;
1108 let mut keys: Vec<K> = Vec::with_capacity(self.n_keys);
1109 let mut values: Vec<T> = Vec::with_capacity(self.n_keys);
1110 for (i, slot) in self.index.into_iter().enumerate() {
1111 if i == 0 { continue; } // dummy
1112 if let Some((off, len, val)) = slot {
1113 keys.push(K::from_bytes(&buf[off.get()..off.get() + len.as_usize()]));
1114 values.push(val);
1115 }
1116 }
1117 (keys, values)
1118 }
1119
1120 // -----------------------------------------------------------------------
1121 // Capacity
1122 // -----------------------------------------------------------------------
1123
1124 pub fn near_capacity(&self) -> bool {
1125 // Arena child addresses and key indices are nonzero and must fit in PTR.
1126 self.arena.len() >= PTR::max_value() || self.index.len() >= PTR::max_value()
1127 }
1128
1129 // -----------------------------------------------------------------------
1130 // Optimize (DFS key-sorted buf rewrite + sparse 2*i+1 index re-spread)
1131 // -----------------------------------------------------------------------
1132
1133 /// Rewrite `buf` in DFS (key-sorted) order and re-spread `index` into a
1134 /// sparse layout: a fresh vec of capacity `2*n+1` with each key placed at
1135 /// slot `2*i+1` (DFS rank `i`), leaving even slots as `None` gaps. Forward
1136 /// iteration then hits `buf` in ascending memory order, and the gaps give
1137 /// future inserts room to shift into without re-sorting.
1138 ///
1139 /// Also (re)establishes the leftmost-`leaf` invariant: every node's `leaf`
1140 /// is set to the key index of the leftmost key in its subtree. The arena
1141 /// topology (child structure) is unchanged — only key indices are remapped.
1142 /// Idempotent.
1143 pub fn optimize(&mut self) {
1144 if self.arena.is_empty() {
1145 return;
1146 }
1147
1148 let n = self.n_keys;
1149 let cap = 2 * n + 1;
1150 // Build a gap-filled vec without requiring T: Clone (vec![None; cap] would).
1151 let mut new_index: Vec<Option<Slot<LEN, T>>> = (0..cap).map(|_| None).collect();
1152 let mut new_buf: Vec<u8> = Vec::with_capacity(self.buf.len());
1153 new_buf.push(0); // dummy byte at position 0
1154 let mut cursor: usize = 1;
1155 let mut i: usize = 0; // global DFS rank
1156
1157 self.walk_optimize(0, &mut new_index, &mut new_buf, &mut cursor, &mut i);
1158
1159 new_buf.truncate(cursor);
1160 self.buf = new_buf;
1161 self.index = new_index;
1162 // NOTE: `flatten()` is NOT called here yet. Wiring it in makes `optimize`
1163 // produce Fnodes, but `insert` (step 5) is still Inode-only and panics on
1164 // an Fnode — so insert-after-optimize would break until the
1165 // `fnode_mode::OptimizeOnly` expand-on-write path lands. `walk_optimize`
1166 // already remaps Fnode `base`+offsets, so a *standalone* `flatten` →
1167 // `optimize` → `flatten` cycle is correct; call `flatten()` explicitly.
1168 }
1169
1170 /// DFS walk that places each key at `2*i+1` in `new_index`, copies its bytes
1171 /// contiguously into `new_buf`, rewrites the arena's key-index references
1172 /// (`children[nib]` for leaf children, `leaf` for the node's leftmost,
1173 /// `base`+offsets for Fnode children), and returns the slot of the leftmost
1174 /// key placed in this subtree.
1175 fn walk_optimize(
1176 &mut self,
1177 phys_idx: usize,
1178 new_index: &mut Vec<Option<Slot<LEN, T>>>,
1179 new_buf: &mut Vec<u8>,
1180 cursor: &mut usize,
1181 i: &mut usize,
1182 ) -> usize {
1183 // `Node: Copy`, so copy the inner Inode out to avoid borrow conflicts
1184 // while we recurse (which needs `&mut self`) and rewrite arena slots.
1185 let node = *self.inode(phys_idx);
1186 let mut first: Option<usize> = None;
1187
1188 // This node's own terminal key sorts before all its descendants.
1189 if node.is_terminal() {
1190 let slot = self.place_key(node.leaf.get().as_usize(), new_index, new_buf, cursor, i);
1191 first = Some(slot);
1192 }
1193
1194 // Visit children in nibble order (== sorted order); leaf children become
1195 // keys, internal children (Inode or Fnode) are recursed into / remapped.
1196 for nib in 0..16 {
1197 if !node.is_occupied(nib) {
1198 continue;
1199 }
1200 let child_phys = node.children[nib].get().as_usize();
1201 if node.is_leaf(nib) {
1202 let slot = self.place_key(child_phys, new_index, new_buf, cursor, i);
1203 self.inode_mut(phys_idx).children[nib] = OptNz::from_index(PTR::from_usize(slot));
1204 if first.is_none() {
1205 first = Some(slot);
1206 }
1207 } else {
1208 let child_first = match self.arena[child_phys] {
1209 ArenaNode::Inode(_) => {
1210 self.walk_optimize(child_phys, new_index, new_buf, cursor, i)
1211 }
1212 // An Fnode child: place its keys in pre-order (base then
1213 // array terminal slots) and remap `base`+offsets to the new
1214 // `2i+1` slots. Branch markers place no key (offset stays
1215 // `0xFF`). The Fnode is a DAG leaf — no arena refs to fix.
1216 ArenaNode::Fnode(f) => {
1217 let old_base = f.base.as_usize();
1218 let new_base = self.place_key(old_base, new_index, new_buf, cursor, i);
1219 let mut new_slots: TinyArray<(LEN, u8), FNODE_SLOTS> = TinyArray::new();
1220 for (plen, offset) in f.slots.as_slice() {
1221 if *offset == FNODE_OFFSET_NULL {
1222 // Branch marker — no key to place.
1223 new_slots.push((*plen, FNODE_OFFSET_NULL));
1224 } else if *offset == 0 {
1225 // The base key itself (terminal=false case: `base`
1226 // is an array slot at offset 0). Already placed
1227 // above as `new_base`; keep it at offset 0.
1228 new_slots.push((*plen, 0));
1229 } else {
1230 let old_ki = old_base + *offset as usize;
1231 let new_slot = self.place_key(old_ki, new_index, new_buf, cursor, i);
1232 new_slots.push((*plen, (new_slot - new_base) as u8));
1233 }
1234 }
1235 self.arena[child_phys] = ArenaNode::Fnode(FlatNode {
1236 nibbles: f.nibbles,
1237 base: PTR::from_usize(new_base),
1238 terminal: f.terminal,
1239 slots: new_slots,
1240 });
1241 new_base
1242 }
1243 };
1244 if first.is_none() {
1245 first = Some(child_first);
1246 }
1247 }
1248 }
1249
1250 let leftmost = first.expect("walk_optimize: node must have at least one key in subtree");
1251 self.inode_mut(phys_idx).leaf = OptNz::from_index(PTR::from_usize(leftmost));
1252 leftmost
1253 }
1254
1255 /// Place the key currently at `old_ki` into `new_index`/`new_buf` at slot
1256 /// `2*i+1` (advancing `i`), copy its bytes contiguously, and return the new
1257 /// slot. Takes the old slot out of `index` (so the value moves, no `T:
1258 /// Clone`). Shared by the Inode terminal/leaf-child paths and the Fnode
1259 /// base/slot paths of [`walk_optimize`](Self::walk_optimize).
1260 fn place_key(
1261 &mut self,
1262 old_ki: usize,
1263 new_index: &mut Vec<Option<Slot<LEN, T>>>,
1264 new_buf: &mut Vec<u8>,
1265 cursor: &mut usize,
1266 i: &mut usize,
1267 ) -> usize {
1268 let slot = 2 * *i + 1;
1269 *i += 1;
1270 let (off, len, val) = self.index[old_ki].take().unwrap();
1271 let old_off = off.get();
1272 let start = *cursor;
1273 new_buf.resize(start + len.as_usize(), 0);
1274 new_buf[start..start + len.as_usize()]
1275 .copy_from_slice(&self.buf[old_off..old_off + len.as_usize()]);
1276 *cursor = start + len.as_usize();
1277 new_index[slot] = Some((NonZero::new(start).unwrap(), len, val));
1278 slot
1279 }
1280
1281 /// Flatten small multi-Inode subtrees into single [`FlatNode`]s.
1282 ///
1283 /// Rebuilds the arena top-down: any non-root subtree with ≤ [`FNODE_CAP`]
1284 /// keys and ≥ 2 Inodes (so collapsing it actually saves memory) is replaced
1285 /// by one Fnode built from a pre-order DFS of that subtree. The root stays
1286 /// an Inode. This is an **arena-only** rebuild — key indices (leaf children,
1287 /// each Inode's `leaf`, and Fnode `base`/offsets) are unchanged; only
1288 /// internal-child arena indices are remapped. So `index`/`buf` are untouched
1289 /// and the existing `get`/`iter`/`seek` paths work unchanged.
1290 ///
1291 /// Idempotent: an Fnode holds no arena refs, so a subtree already containing
1292 /// an Fnode can't be re-flattened (the Fnode is copied verbatim and
1293 /// [`build_fnode_subtree`] rejects Fnode children). Calling `flatten` on an
1294 /// already-flat trie is a no-op topology copy.
1295 ///
1296 /// Best called after [`optimize`](Self::optimize): the `2i+1` respread
1297 /// makes a ≤16-key subtree span exactly ≤32 `index` slots, so every offset
1298 /// fits in `u8` with room (≤ 30 ≪ `0xFF`), and offsets come out canonical
1299 /// even values `0, 2, 4, …`.
1300 pub fn flatten(&mut self) {
1301 if self.arena.is_empty() {
1302 return;
1303 }
1304 // Pass 1: count keys & Inodes per subtree (bottom-up), keyed by old phys.
1305 // `(0, 0)` is the "not yet counted" sentinel (every real subtree has ≥1
1306 // key and ≥1 Inode).
1307 let mut counts: Vec<(usize, usize)> = vec![(0, 0); self.arena.len()];
1308 self.count_subtree(0, &mut counts);
1309 // Pass 2: rebuild the arena top-down, flattening qualifying subtrees.
1310 let mut new_arena: Vec<ArenaNode<PTR, LEN>> = Vec::with_capacity(self.arena.len());
1311 self.rebuild_subtree(0, &mut new_arena, &counts);
1312 self.arena = new_arena;
1313 }
1314
1315 /// Bottom-up subtree key/Inode counts. Fills `counts[phys] = (n_keys,
1316 /// n_inodes)` for `phys` and every descendant. Fnodes are DAG leaves (no
1317 /// arena children): they contribute their [`FlatNode::key_count`] and 1
1318 /// Inode-equivalent.
1319 fn count_subtree(&self, phys: usize, counts: &mut [(usize, usize)]) {
1320 if counts[phys] != (0, 0) {
1321 return; // already counted (defensive — a tree, so reached once)
1322 }
1323 let (keys, inodes) = match &self.arena[phys] {
1324 ArenaNode::Fnode(f) => (f.key_count(), 1),
1325 ArenaNode::Inode(node) => {
1326 let mut k = if node.is_terminal() { 1 } else { 0 };
1327 let mut i = 1;
1328 for nib in 0..16 {
1329 if !node.is_occupied(nib) {
1330 continue;
1331 }
1332 if node.is_leaf(nib) {
1333 k += 1;
1334 } else {
1335 let child = node.children[nib].get().as_usize();
1336 self.count_subtree(child, counts);
1337 let (ck, ci) = counts[child];
1338 k += ck;
1339 i += ci;
1340 }
1341 }
1342 (k, i)
1343 }
1344 };
1345 counts[phys] = (keys, inodes);
1346 }
1347
1348 /// Rebuild the subtree rooted at old `phys` into `new_arena`, returning its
1349 /// new arena index. Flattens qualifying non-root subtrees into one Fnode
1350 /// (consuming their old child Inodes — no orphans); otherwise copies the
1351 /// Inode and recurses, remapping internal-child arena indices. Fnode
1352 /// children are copied verbatim (no arena refs to remap). Leaf children and
1353 /// `leaf` are key indices, unchanged by this arena-only rebuild.
1354 fn rebuild_subtree(
1355 &self,
1356 phys: usize,
1357 new_arena: &mut Vec<ArenaNode<PTR, LEN>>,
1358 counts: &[(usize, usize)],
1359 ) -> usize {
1360 if let ArenaNode::Fnode(f) = &self.arena[phys] {
1361 new_arena.push(ArenaNode::Fnode(*f));
1362 return new_arena.len() - 1;
1363 }
1364 let node = *self.inode(phys);
1365 let (n_keys, n_inodes) = counts[phys];
1366 // Flatten qualifying subtrees. The root (phys == 0) is always kept an
1367 // Inode. `build_fnode_subtree` may still reject (Fnode child / offset
1368 // overflow); then fall through to copy + recurse.
1369 if phys != 0 && n_keys <= FNODE_CAP && n_inodes >= 2 {
1370 if let Some(fnode) = self.build_fnode_subtree(phys) {
1371 new_arena.push(ArenaNode::Fnode(fnode));
1372 return new_arena.len() - 1;
1373 }
1374 }
1375 // Copy the Inode (with old internal-child indices) and remap its internal
1376 // children. Leaf-child slots and `leaf` carry key indices — unchanged.
1377 let new_phys = new_arena.len();
1378 new_arena.push(ArenaNode::Inode(node));
1379 for nib in 0..16 {
1380 if node.is_occupied(nib) && !node.is_leaf(nib) {
1381 let child_old = node.children[nib].get().as_usize();
1382 let child_new = self.rebuild_subtree(child_old, new_arena, counts);
1383 match &mut new_arena[new_phys] {
1384 ArenaNode::Inode(n) => {
1385 n.children[nib] = OptNz::from_index(PTR::from_usize(child_new))
1386 }
1387 _ => unreachable!("rebuild_subtree: placeholder was Inode"),
1388 }
1389 }
1390 }
1391 new_phys
1392 }
1393
1394 /// Build a [`FlatNode`] from the Inode subtree rooted at `phys`: pre-order
1395 /// DFS collecting `(prefix_len, key_index)` per array slot, with `base` =
1396 /// the root's `leaf` (leftmost key) and `terminal` = the root's own
1397 /// terminal flag. Returns `None` if `phys` is not an Inode, the subtree
1398 /// contains an Fnode child (merging Fnodes is not supported), the slot count
1399 /// would exceed [`FNODE_SLOTS`], or an offset would collide with the `0xFF`
1400 /// sentinel. Does NOT check `phys != 0` — the root-stays-Inode invariant is
1401 /// the caller's responsibility.
1402 fn build_fnode_subtree(&self, phys: usize) -> Option<FlatNode<PTR, LEN>> {
1403 if !matches!(self.arena[phys], ArenaNode::Inode(_)) {
1404 return None;
1405 }
1406 let root = *self.inode(phys);
1407 let base = root.leaf.get();
1408 let terminal = root.is_terminal();
1409 let mut plens: Vec<LEN> = Vec::new();
1410 let mut key_idxs: Vec<Option<PTR>> = Vec::new();
1411 let mut nibbles: u64 = 0;
1412 let mut ok = true;
1413 self.collect_flat_slots(phys, &mut plens, &mut key_idxs, &mut nibbles, &mut ok);
1414 if !ok || plens.is_empty() || plens.len() > FNODE_SLOTS {
1415 return None;
1416 }
1417 let base_u = base.as_usize();
1418 let mut slots: TinyArray<(LEN, u8), FNODE_SLOTS> = TinyArray::new();
1419 for (plen, kidx) in plens.into_iter().zip(key_idxs.into_iter()) {
1420 let offset = match kidx {
1421 None => FNODE_OFFSET_NULL,
1422 Some(ki) => {
1423 let off = ki.as_usize() - base_u;
1424 if off >= FNODE_OFFSET_NULL as usize {
1425 return None;
1426 }
1427 off as u8
1428 }
1429 };
1430 slots.push((plen, offset));
1431 }
1432 Some(FlatNode { nibbles, base, terminal, slots })
1433 }
1434
1435 /// Pre-order DFS collecting the subtree at `phys` into `plens`/`key_idxs`/
1436 /// `nibbles` (per-slot `(prefix_len, Option<key_index>)`). Sets `ok = false`
1437 /// and returns early on: an Fnode child (can't merge), or the slot count
1438 /// exceeding [`FNODE_SLOTS`]. A terminal+branch internal child emits its
1439 /// own key (`Some`) as the edge slot, then its descendants; a pure branch
1440 /// emits `None`. For a terminal subtree root, the root's own key is pulled
1441 /// out into `base`/`terminal` (by the caller) and NOT emitted here.
1442 fn collect_flat_slots(
1443 &self,
1444 phys: usize,
1445 plens: &mut Vec<LEN>,
1446 key_idxs: &mut Vec<Option<PTR>>,
1447 nibbles: &mut u64,
1448 ok: &mut bool,
1449 ) {
1450 let node = *self.inode(phys);
1451 let p = node.prefix_len;
1452 for nib in 0..16 {
1453 if !node.is_occupied(nib) {
1454 continue;
1455 }
1456 let i = plens.len();
1457 if i >= FNODE_SLOTS {
1458 *ok = false;
1459 return;
1460 }
1461 *nibbles |= (nib as u64) << (4 * i);
1462 if node.is_leaf(nib) {
1463 plens.push(p);
1464 key_idxs.push(Some(node.children[nib].get()));
1465 } else {
1466 let child = node.children[nib].get().as_usize();
1467 if matches!(self.arena[child], ArenaNode::Fnode(_)) {
1468 *ok = false;
1469 return;
1470 }
1471 let child_node = *self.inode(child);
1472 let ptr = if child_node.is_terminal() {
1473 Some(child_node.leaf.get())
1474 } else {
1475 None
1476 };
1477 plens.push(p);
1478 key_idxs.push(ptr);
1479 self.collect_flat_slots(child, plens, key_idxs, nibbles, ok);
1480 if !*ok {
1481 return;
1482 }
1483 }
1484 }
1485 }
1486}
1487
1488impl<K: ByteKey, T, PTR: TrieIndex, LEN: TrieIndex> Default for NibbleTrie<K, T, PTR, LEN> {
1489 fn default() -> Self { Self::new() }
1490}
1491
1492// ---------------------------------------------------------------------------
1493// Insertion (Stage B: shift-based slot allocation + bump walk)
1494// ---------------------------------------------------------------------------
1495
1496/// The resolved insertion case, produced by a non-mutating descent
1497/// (`find_insert_case`) BEFORE any arena/index mutation. All sub-cases and
1498/// nibble values are read from the pre-mutation tree, so the case stays valid
1499/// across the slot shift + bump (which only remap key indices, never arena
1500/// topology or nibble positions). The one exception — `SplitLeaf`'s existing
1501/// key index — is re-read from the arena in `execute_case` (post-bump) rather
1502/// than captured here, because that leaf slot may be the successor `p` and get
1503/// bumped from `p` to `p+1`.
1504enum Case {
1505 /// New key is a prefix of the node's reference key → `phys` becomes terminal.
1506 Terminal { phys: usize },
1507 /// New key diverges at `phys.prefix_len` into an empty nibble slot → leaf child.
1508 NewLeafChild { phys: usize, nib: usize },
1509 /// New key diverges from the node's reference key mid-prefix → split `phys`
1510 /// into a new parent (at `diverge`) holding the new key and the old subtree.
1511 SplitNode {
1512 phys: usize,
1513 diverge: usize,
1514 new_is_terminal: bool,
1515 new_nib: usize,
1516 ref_nib: usize,
1517 new_is_leftmost: bool,
1518 },
1519 /// New key diverges from an existing leaf child of `phys` at nibble `nib`
1520 /// → replace that leaf with a new split node holding both keys.
1521 SplitLeaf {
1522 phys: usize,
1523 nib: usize,
1524 d: usize,
1525 new_is_terminal: bool,
1526 existing_is_terminal: bool,
1527 new_nib: usize,
1528 exist_nib: usize,
1529 new_is_leftmost: bool,
1530 },
1531}
1532
1533impl<K: ByteKey, T, PTR: TrieIndex, LEN: TrieIndex> NibbleTrie<K, T, PTR, LEN> {
1534 pub fn insert(&mut self, key: K, value: T) -> Result<usize, ()> {
1535 let key_bytes = key.bytes();
1536 // Overflow checks: arena/key indices must fit in PTR (nonzero, so < max).
1537 if self.arena.len() >= PTR::max_value() || self.index.len() >= PTR::max_value() {
1538 return Err(());
1539 }
1540 if key_bytes.len() * 2 > LEN::max_value() {
1541 return Err(());
1542 }
1543
1544 // 90% capacity trigger: when the sparse index or buf is nearly full,
1545 // re-spread into a fresh 2n+1 layout so future shifts have gaps to land in.
1546 // Skip the re-spread if `2n+1` would overflow PTR (`2n < max` ⟺ `2n+1 <= max`,
1547 // then the trie is simply near its index capacity; the overflow checks below
1548 // return Err instead).
1549 if self.needs_optimize() && 2 * self.n_keys < PTR::max_value() {
1550 self.optimize();
1551 }
1552 // Overflow checks: arena/key indices must fit in PTR (nonzero, so < max).
1553 if self.arena.len() >= PTR::max_value() || self.index.len() >= PTR::max_value() {
1554 return Err(());
1555 }
1556
1557 let key_len = LEN::from_usize(key_bytes.len());
1558 let off = self.buf.len();
1559 self.buf.extend_from_slice(key_bytes);
1560 // buf[0] is the dummy byte, so every real key offset is >= 1 → NonZero.
1561 self.n_keys += 1;
1562 let max_nib = key_bytes.len() * 2;
1563
1564 if self.arena.is_empty() {
1565 return Ok(self.insert_into_empty_trie(off, key_len, value, key_bytes, max_nib));
1566 }
1567
1568 // 1. Detect: non-mutating descent resolves the case + the descent path.
1569 let (case, path) = match self.find_insert_case(key_bytes, max_nib) {
1570 Ok(c) => c,
1571 Err(()) => {
1572 // Duplicate — no slot was pushed yet (slot alloc happens below),
1573 // so rollback just drops the buf extend and the key count.
1574 self.buf.truncate(off);
1575 self.n_keys -= 1;
1576 return Err(());
1577 }
1578 };
1579
1580 // 2. Compute p: the slot the successor key currently occupies (the new
1581 // key sorts into position p, shifting [p, p+n-1] right). None = END
1582 // (new key is the largest → append, no shift, no bump).
1583 let p_opt = self.compute_p(&case, &path);
1584 let (p, n) = match p_opt {
1585 None => {
1586 let p = self.index.len();
1587 self.index
1588 .push(Some((NonZero::new(off).unwrap(), key_len, value)));
1589 self.execute_case(case, p, &path);
1590 return Ok(p);
1591 }
1592 Some(p) => {
1593 // Scan forward from p, counting occupied slots until the first gap.
1594 // (All keys from p onward are contiguous until a None gap — the
1595 // successor and its trailing run that must shift right by one.)
1596 let mut n = 0;
1597 while p + n < self.index.len() && self.index[p + n].is_some() {
1598 n += 1;
1599 }
1600 (p, n)
1601 }
1602 };
1603
1604 // Ensure room for the shift: the trailing gap may lie past `index.len()`.
1605 if p + n >= self.index.len() {
1606 self.index.push(None);
1607 }
1608
1609 if n > 0 {
1610 // 3. Position a forward walk at the successor key (slot p) by seeking.
1611 // The seek borrows self immutably; copy out the (all-Copy) stack
1612 // and drop the borrow before mutating.
1613 let succ_bytes = {
1614 let (soff, slen, _) = self.index[p].as_ref().unwrap();
1615 self.buf[soff.get()..soff.get() + slen.as_usize()].to_vec()
1616 };
1617 let stack: Vec<(usize, u16, usize)> = {
1618 let mut it = self.walk_iter();
1619 it.seek(&succ_bytes);
1620 debug_assert_eq!(
1621 it.current_index(),
1622 Some(p),
1623 "seek must land on the successor slot"
1624 );
1625 it.stack
1626 .iter()
1627 .map(|frame| match *frame {
1628 Frame::Inode { encoded, mask, nib } => (encoded.as_usize(), mask, nib),
1629 // Fnode frames can't reach bump_walk yet — inserts never
1630 // touch an Fnode until step 5 wires flat_insert/split.
1631 Frame::Fnode { .. } => panic!(
1632 "bump_walk init: Fnode frame on stack — insert-into-Fnode is step 5"
1633 ),
1634 })
1635 .collect()
1636 };
1637
1638 // 4. Bump arena refs whose key index ∈ [p, p+n-1] (every shifted key's
1639 // structural ptr + every node whose leftmost is a shifted key).
1640 self.bump_walk(stack, p, n);
1641
1642 // 5. Shift the slots right by one. A `take()` walk from the right end
1643 // (not `copy_within`, which needs `T: Copy`) — a true element-wise
1644 // move that leaves `None` at `p` for the new slot.
1645 for i in (0..n).rev() {
1646 self.index[p + i + 1] = self.index[p + i].take();
1647 }
1648 }
1649
1650 // 6. Place the new key's slot at p.
1651 self.index[p] = Some((NonZero::new(off).unwrap(), key_len, value));
1652
1653 // 7. Wire the new key into the arena at slot p (re-reading any
1654 // bump-sensitive leaf index from the arena post-bump), then propagate
1655 // the leftmost-`leaf` invariant up the spine.
1656 self.execute_case(case, p, &path);
1657 Ok(p)
1658 }
1659
1660 /// 90% capacity trigger. Measures fill as `n_keys / index.capacity()` (NOT
1661 /// `len / capacity`): after `optimize`, `len == capacity` because the gaps
1662 /// are real `None` slots, so `len` would always read as 100% full.
1663 #[inline]
1664 fn needs_optimize(&self) -> bool {
1665 let idx_cap = self.index.capacity();
1666 let buf_cap = self.buf.capacity();
1667 (idx_cap > 0 && 10 * self.n_keys > 9 * idx_cap)
1668 || (buf_cap > 0 && 10 * self.buf.len() > 9 * buf_cap)
1669 }
1670
1671 /// Non-mutating descent mirroring the lookup walk, but it RECORDS the
1672 /// resolved `Case` and descent `path` instead of mutating. Reads the
1673 /// reference/existing keys here (before any shift moves their slots).
1674 /// Returns `Err(())` for duplicates.
1675 fn find_insert_case(
1676 &self,
1677 key: &[u8],
1678 max_nib: usize,
1679 ) -> Result<(Case, Vec<(usize, usize)>), ()> {
1680 let mut phys_idx: usize = 0;
1681 let mut confirmed: usize = 0;
1682 // Path of (ancestor_phys, nib_used_to_descend) from root to the current
1683 // node, used to propagate the leftmost-`leaf` invariant up the spine.
1684 let mut path: Vec<(usize, usize)> = Vec::new();
1685
1686 loop {
1687 let node = self.inode(phys_idx);
1688 let ki = node.leaf.get();
1689 let (off, ref_len, _) = self.index[ki.as_usize()].as_ref().unwrap();
1690 let off = off.get();
1691 let ref_key = &self.buf[off..off + ref_len.as_usize()];
1692 let prefix_len = node.prefix_len.as_usize();
1693
1694 match simd_check_prefix::<8>(key, ref_key, confirmed, prefix_len) {
1695 PrefixCheck::Diverges(diverge) => {
1696 let new_nib = key_nibble_at(key, diverge) as usize;
1697 let ref_nib = key_nibble_at(ref_key, diverge) as usize;
1698 let new_is_terminal = diverge >= max_nib;
1699 let new_is_leftmost = new_is_terminal || new_nib < ref_nib;
1700 return Ok((
1701 Case::SplitNode {
1702 phys: phys_idx,
1703 diverge,
1704 new_is_terminal,
1705 new_nib,
1706 ref_nib,
1707 new_is_leftmost,
1708 },
1709 path,
1710 ));
1711 }
1712 PrefixCheck::Matches => {
1713 if max_nib == prefix_len {
1714 if key.len() == ref_key.len() {
1715 return Err(()); // exact duplicate
1716 }
1717 // New key is a prefix of the ref key → node becomes terminal.
1718 return Ok((Case::Terminal { phys: phys_idx }, path));
1719 }
1720
1721 confirmed = prefix_len + 1;
1722 let nib = key_nibble_at(key, prefix_len) as usize;
1723 if !node.is_occupied(nib) {
1724 // Empty slot — new key diverges here as a leaf child.
1725 return Ok((Case::NewLeafChild { phys: phys_idx, nib }, path));
1726 }
1727
1728 if node.is_leaf(nib) {
1729 // Split the existing leaf child: resolve divergence here.
1730 path.push((phys_idx, nib));
1731 let existing_key_index = node.children[nib].get();
1732 let (eo, elen, _) =
1733 self.index[existing_key_index.as_usize()].as_ref().unwrap();
1734 let existing_key = &self.buf[eo.get()..eo.get() + elen.as_usize()];
1735 match simd_find_divergence::<8>(key, existing_key, confirmed) {
1736 DivergeResult::Duplicate => return Err(()),
1737 DivergeResult::At(d) => {
1738 let new_is_terminal = d >= max_nib;
1739 let existing_is_terminal = d >= existing_key.len() * 2;
1740 let new_nib = key_nibble_at(key, d) as usize;
1741 let exist_nib = key_nibble_at(existing_key, d) as usize;
1742 let new_is_leftmost = if new_is_terminal {
1743 true
1744 } else if existing_is_terminal {
1745 false
1746 } else {
1747 new_nib < exist_nib
1748 };
1749 return Ok((
1750 Case::SplitLeaf {
1751 phys: phys_idx,
1752 nib,
1753 d,
1754 new_is_terminal,
1755 existing_is_terminal,
1756 new_nib,
1757 exist_nib,
1758 new_is_leftmost,
1759 },
1760 path,
1761 ));
1762 }
1763 }
1764 }
1765
1766 path.push((phys_idx, nib));
1767 phys_idx = node.children[nib].get().as_usize();
1768 }
1769 }
1770 }
1771 }
1772
1773 /// Compute `p`: the key index of the successor (the leftmost key that sorts
1774 /// STRICTLY AFTER the new key). The new key takes slot `p`, shifting the
1775 /// successor and its trailing run right. `None` means the new key is the
1776 /// largest (END — append, no shift). Reads only pre-mutation state.
1777 fn compute_p(&self, case: &Case, path: &[(usize, usize)]) -> Option<usize> {
1778 match case {
1779 Case::Terminal { phys } => Some(self.inode(*phys).leaf.get().as_usize()),
1780 Case::NewLeafChild { phys, nib } => self.right_anchor(*phys, *nib, path),
1781 Case::SplitNode {
1782 phys,
1783 new_is_terminal,
1784 new_nib,
1785 ref_nib,
1786 ..
1787 } => {
1788 if *new_is_terminal || *new_nib < *ref_nib {
1789 // New key is the new leftmost of `phys`'s subtree → successor
1790 // is the old leftmost (the ref key), read before mutation.
1791 Some(self.inode(*phys).leaf.get().as_usize())
1792 } else {
1793 self.subtree_successor(path)
1794 }
1795 }
1796 Case::SplitLeaf {
1797 phys,
1798 nib,
1799 new_is_terminal,
1800 existing_is_terminal,
1801 new_nib,
1802 exist_nib,
1803 ..
1804 } => {
1805 let existing_key_index = self.inode(*phys).children[*nib].get().as_usize();
1806 if *new_is_terminal {
1807 Some(existing_key_index)
1808 } else if *existing_is_terminal {
1809 self.right_anchor(*phys, *nib, path)
1810 } else if *new_nib < *exist_nib {
1811 Some(existing_key_index)
1812 } else {
1813 self.right_anchor(*phys, *nib, path)
1814 }
1815 }
1816 }
1817 }
1818
1819 /// The leftmost key index of the next-higher subtree at `phys` (relative to
1820 /// nib `nib`), i.e. the successor of a key ending at `phys` via `nib`. Falls
1821 /// back to `subtree_successor` if `phys` has no higher occupied nibble.
1822 /// Uses the leftmost-`leaf` invariant: an internal child's `leaf` is its
1823 /// subtree's leftmost key index.
1824 fn right_anchor(&self, phys: usize, nib: usize, path: &[(usize, usize)]) -> Option<usize> {
1825 let mask = self.inode(phys).children_mask();
1826 let higher = if nib >= 15 { 0u16 } else { mask & !((1u16 << (nib + 1)) - 1) };
1827 if higher != 0 {
1828 let next_nib = higher.trailing_zeros() as usize;
1829 let r = self.inode(phys).children[next_nib].get();
1830 Some(if self.inode(phys).is_leaf(next_nib) {
1831 r.as_usize()
1832 } else {
1833 self.inode(r.as_usize()).leaf.get().as_usize()
1834 })
1835 } else {
1836 self.subtree_successor(path)
1837 }
1838 }
1839
1840 /// Walk up `path` (deepest first); at each `(parent, nib)` find a higher
1841 /// occupied nibble than the one descended through. The leftmost of that
1842 /// higher subtree is the successor. `None` = no higher ancestor nibble =
1843 /// the new key is the largest (END).
1844 fn subtree_successor(&self, path: &[(usize, usize)]) -> Option<usize> {
1845 for &(parent, nib) in path.iter().rev() {
1846 let mask = self.inode(parent).children_mask();
1847 let higher = if nib >= 15 { 0u16 } else { mask & !((1u16 << (nib + 1)) - 1) };
1848 if higher != 0 {
1849 let next_nib = higher.trailing_zeros() as usize;
1850 let r = self.inode(parent).children[next_nib].get();
1851 return Some(if self.inode(parent).is_leaf(next_nib) {
1852 r.as_usize()
1853 } else {
1854 self.inode(r.as_usize()).leaf.get().as_usize()
1855 });
1856 }
1857 }
1858 None
1859 }
1860
1861 /// Bump every arena ref whose key index ∈ [lo, lo+n-1]: each shifted key's
1862 /// structural ptr (terminal → `node.leaf`, leaf child → `node.children[nib]`)
1863 /// and every node whose `leaf` (leftmost) is a shifted key.
1864 ///
1865 /// Done as a forward DFS walk from slot `lo` for exactly `n` keys, mirroring
1866 /// `NibbleIter`'s advance/push_next_child/descend_first but with direct
1867 /// `&mut self` arena mutation. Navigation stays safe mid-walk: internal
1868 /// `children[nib]` are arena indices (unchanged by a key-index shift); leaf
1869 /// `children[nib]` are terminal for navigation; `leaf` is never traversed.
1870 ///
1871 /// Bumping rule (unified, avoids double-bumping terminal nodes whose
1872 /// `leaf` IS their structural ptr): bump `leaf` of EVERY touched node whose
1873 /// `leaf ∈ [lo,hi]` (seek-path ancestors + nodes entered via descend_first),
1874 /// and bump `children[nib]` for each visited leaf-child key. Terminal keys'
1875 /// structural ptr is their node's `leaf`, bumped once by the first rule.
1876 fn bump_walk(&mut self, init_stack: Vec<(usize, u16, usize)>, lo: usize, n: usize) {
1877 debug_assert!(n >= 1);
1878 let hi = lo + n - 1; // inclusive
1879 let mut stack = init_stack;
1880
1881 // Bump `leaf` of every node on the initial (seek) stack if in range.
1882 // These are the ancestors of `lo` plus `lo`'s owning node.
1883 for &(phys, _mask, _nib) in &stack {
1884 let l = self.inode(phys).leaf.get().as_usize();
1885 if l >= lo && l <= hi {
1886 self.inode_mut(phys).leaf = OptNz::from_index(PTR::from_usize(l + 1));
1887 }
1888 }
1889
1890 // Walk forward exactly n keys, bumping each leaf-child structural ptr.
1891 let mut seen = 0;
1892 while seen < n {
1893 let &(phys, _mask, nib) = stack.last().expect("bump_walk: stack emptied early");
1894 if nib == TERMINAL_NIB {
1895 // Terminal key: its structural ptr is `arena[phys].leaf`, already
1896 // bumped above (when this frame was pushed — its leaf == this
1897 // key's index, which is in range).
1898 seen += 1;
1899 } else {
1900 let k = self.inode(phys).children[nib].get().as_usize();
1901 // k ∈ [lo,hi] by construction (we visit exactly the shifted run).
1902 self.inode_mut(phys).children[nib] = OptNz::from_index(PTR::from_usize(k + 1));
1903 seen += 1;
1904 }
1905 if seen == n {
1906 break;
1907 }
1908 if !self.bump_advance(&mut stack, lo, hi) {
1909 debug_assert!(seen >= n, "bump_walk: tree exhausted before n keys");
1910 break;
1911 }
1912 }
1913 }
1914
1915 /// `descend_first` with `leaf`-bumping: walk down the lowest-nib spine of
1916 /// the subtree at `phys`, pushing a frame per node and bumping each node's
1917 /// `leaf` if in range, until a terminal key or a leaf-child is current.
1918 fn bump_descend_first(
1919 &mut self,
1920 stack: &mut Vec<(usize, u16, usize)>,
1921 mut phys: usize,
1922 lo: usize,
1923 hi: usize,
1924 ) {
1925 loop {
1926 // `Node: Copy` — copy the inner Inode out so we can mutate the
1927 // arena slot (bumping `leaf`) and re-loop without borrow conflicts.
1928 let node = *self.inode(phys);
1929 let l = node.leaf.get().as_usize();
1930 if l >= lo && l <= hi {
1931 self.inode_mut(phys).leaf = OptNz::from_index(PTR::from_usize(l + 1));
1932 }
1933 if node.is_terminal() {
1934 let mask = node.children_mask();
1935 stack.push((phys, mask, TERMINAL_NIB));
1936 return;
1937 }
1938 let mask = node.children_mask();
1939 debug_assert!(mask != 0, "bump_descend_first: non-terminal node with no children");
1940 let nib = mask.trailing_zeros() as usize;
1941 stack.push((phys, mask, nib));
1942 if node.is_leaf(nib) {
1943 return;
1944 } else {
1945 phys = node.children[nib].get().as_usize();
1946 }
1947 }
1948 }
1949
1950 /// `push_next_child` with descent: find the next occupied nibble ≥
1951 /// `start_nib` at `encoded`, push its frame, and if it is an internal
1952 /// child, `bump_descend_first` into it. Returns false if no such nibble.
1953 #[inline]
1954 fn bump_push_next(
1955 &mut self,
1956 stack: &mut Vec<(usize, u16, usize)>,
1957 encoded: usize,
1958 mask: u16,
1959 start_nib: usize,
1960 lo: usize,
1961 hi: usize,
1962 ) -> bool {
1963 let shifted = if start_nib >= 16 { 0u16 } else { mask >> start_nib };
1964 if shifted == 0 {
1965 return false;
1966 }
1967 let nib = start_nib + shifted.trailing_zeros() as usize;
1968 debug_assert!(nib < 16);
1969 stack.push((encoded, mask, nib));
1970 if !self.inode(encoded).is_leaf(nib) {
1971 let addr = self.inode(encoded).children[nib].get().as_usize();
1972 self.bump_descend_first(stack, addr, lo, hi);
1973 }
1974 true
1975 }
1976
1977 /// `advance_next` with mutation: pop frames and `bump_push_next` from the
1978 /// next nibble until a key is current. Returns false if the stack empties.
1979 #[inline]
1980 fn bump_advance(
1981 &mut self,
1982 stack: &mut Vec<(usize, u16, usize)>,
1983 lo: usize,
1984 hi: usize,
1985 ) -> bool {
1986 loop {
1987 let (encoded, mask, nib) = match stack.pop() {
1988 Some(v) => v,
1989 None => return false,
1990 };
1991 if nib == TERMINAL_NIB {
1992 if self.bump_push_next(stack, encoded, mask, 0, lo, hi) {
1993 return true;
1994 }
1995 continue;
1996 }
1997 let search_start = if nib == usize::MAX { 0 } else { nib + 1 };
1998 if self.bump_push_next(stack, encoded, mask, search_start, lo, hi) {
1999 return true;
2000 }
2001 }
2002 }
2003
2004 /// Wire the new key (at slot `p`) into the arena according to `case`, then
2005 /// propagate the leftmost-`leaf` invariant up the spine. Re-reads any
2006 /// bump-sensitive leaf key index from the arena (post-bump) instead of using
2007 /// a value captured before the bump — notably `SplitLeaf`'s existing key,
2008 /// which may have been the successor `p` and shifted to `p+1`.
2009 fn execute_case(&mut self, case: Case, p: usize, path: &[(usize, usize)]) {
2010 let p_idx = PTR::from_usize(p);
2011 match case {
2012 Case::Terminal { phys } => {
2013 self.inode_mut(phys).set_terminal(true);
2014 self.inode_mut(phys).leaf = OptNz::from_index(p_idx);
2015 self.up_walk_leftmost(phys, p_idx, path);
2016 }
2017 Case::NewLeafChild { phys, nib } => {
2018 self.inode_mut(phys).set_leaf_child(nib, p_idx);
2019 self.update_leftmost_on_leaf_insert(phys, nib, p_idx, path);
2020 }
2021 Case::SplitNode {
2022 phys,
2023 diverge,
2024 new_is_terminal,
2025 new_nib,
2026 ref_nib,
2027 new_is_leftmost,
2028 } => {
2029 let mut new_parent = Node::new();
2030 new_parent.prefix_len = LEN::from_usize(diverge);
2031 if new_is_terminal {
2032 new_parent.set_terminal(true);
2033 new_parent.leaf = OptNz::from_index(p_idx);
2034 } else {
2035 new_parent.set_leaf_child(new_nib, p_idx);
2036 if new_is_leftmost {
2037 new_parent.leaf = OptNz::from_index(p_idx);
2038 }
2039 }
2040 let old_node = std::mem::replace(&mut self.arena[phys], ArenaNode::Inode(new_parent));
2041 let old_addr = PTR::from_usize(self.arena.len()); // new node index (>= 1)
2042 self.arena.push(old_node);
2043 self.inode_mut(phys).set_internal_child(ref_nib, old_addr);
2044 if new_is_leftmost {
2045 // New key is the new parent's leftmost — propagate up the spine.
2046 // (Overrides the bump's p→p+1 on this spine back to p.)
2047 self.up_walk_leftmost(phys, p_idx, path);
2048 } else {
2049 // Old subtree is leftmost; its leftmost key index lives on in
2050 // the pushed old node and was bumped there if in range.
2051 let child_leaf = self.inode(old_addr.as_usize()).leaf;
2052 self.inode_mut(phys).leaf = child_leaf;
2053 }
2054 }
2055 Case::SplitLeaf {
2056 phys,
2057 nib,
2058 d,
2059 new_is_terminal,
2060 existing_is_terminal,
2061 new_nib,
2062 exist_nib,
2063 new_is_leftmost,
2064 } => {
2065 // Re-read existing key index post-bump (may have been bumped
2066 // from p to p+1 if existing was the successor).
2067 let existing_key_index = self.inode(phys).children[nib].get();
2068 let mut split_node = Node::new();
2069 split_node.prefix_len = LEN::from_usize(d);
2070 if new_is_terminal {
2071 split_node.set_terminal(true);
2072 split_node.leaf = OptNz::from_index(p_idx);
2073 split_node.set_leaf_child(exist_nib, existing_key_index);
2074 } else if existing_is_terminal {
2075 split_node.set_terminal(true);
2076 split_node.leaf = OptNz::from_index(existing_key_index);
2077 split_node.set_leaf_child(new_nib, p_idx);
2078 } else {
2079 split_node.set_leaf_child(new_nib, p_idx);
2080 split_node.set_leaf_child(exist_nib, existing_key_index);
2081 split_node.leaf = OptNz::from_index(if new_is_leftmost {
2082 p_idx
2083 } else {
2084 existing_key_index
2085 });
2086 }
2087 let split_addr = PTR::from_usize(self.arena.len());
2088 self.arena.push(ArenaNode::Inode(split_node));
2089 self.inode_mut(phys).set_internal_child(nib, split_addr);
2090 if new_is_leftmost {
2091 // path.last() == (phys, nib): if split_node is phys's leftmost
2092 // child, propagate the new leftmost up the spine.
2093 self.up_walk_leftmost(split_addr.as_usize(), p_idx, path);
2094 }
2095 }
2096 }
2097 }
2098
2099 /// If the new leaf child at `nib` is the lowest occupied nib of `phys_idx`,
2100 /// it is the node's new leftmost descendant — set `phys_idx.leaf` and
2101 /// propagate the new leftmost up the leftmost spine via `path`.
2102 #[inline]
2103 fn update_leftmost_on_leaf_insert(
2104 &mut self,
2105 phys_idx: usize,
2106 nib: usize,
2107 new_index: PTR,
2108 path: &[(usize, usize)],
2109 ) {
2110 // A terminal node's own key is a prefix of all its descendants, so it
2111 // is always the leftmost — a new leaf child can never precede it.
2112 if self.inode(phys_idx).is_terminal() {
2113 return;
2114 }
2115 let mask = self.inode(phys_idx).children_mask();
2116 let lowest = mask.trailing_zeros() as usize;
2117 if nib == lowest {
2118 self.inode_mut(phys_idx).leaf = OptNz::from_index(new_index);
2119 self.up_walk_leftmost(phys_idx, new_index, path);
2120 }
2121 }
2122
2123 /// Propagate `new_leftmost` up the leftmost spine: for each ancestor in
2124 /// `path` (deepest first) via which we descended through that ancestor's
2125 /// lowest occupied nib, set its `leaf` to `new_leftmost`. Stop at the first
2126 /// ancestor where the descent was not through its leftmost child, OR at a
2127 /// terminal ancestor — a terminal node's own key is a prefix of all its
2128 /// descendants, so it is always that subtree's leftmost and its `leaf` must
2129 /// stay pinned to the terminal key's slot (ancestors above see that same
2130 /// fixed leftmost, so propagation stops entirely there).
2131 #[inline]
2132 fn up_walk_leftmost(&mut self, attach_phys: usize, new_leftmost: PTR, path: &[(usize, usize)]) {
2133 let _ = attach_phys; // attach's parent is path.last(); attach itself already set.
2134 let mut idx = path.len();
2135 while idx > 0 {
2136 idx -= 1;
2137 let (parent_phys, nib) = path[idx];
2138 if self.inode(parent_phys).is_terminal() {
2139 break;
2140 }
2141 let parent_mask = self.inode(parent_phys).children_mask();
2142 let lowest = parent_mask.trailing_zeros() as usize;
2143 if nib == lowest {
2144 self.inode_mut(parent_phys).leaf = OptNz::from_index(new_leftmost);
2145 } else {
2146 break;
2147 }
2148 }
2149 }
2150
2151 // -----------------------------------------------------------------------
2152 // Insert helpers
2153 // -----------------------------------------------------------------------
2154
2155 /// Empty trie: `index` is `[None]` (dummy at 0). Place the key at slot 1
2156 /// and build a root that is terminal (0-length key) or a leaf child.
2157 #[inline]
2158 fn insert_into_empty_trie(
2159 &mut self,
2160 off: usize,
2161 key_len: LEN,
2162 value: T,
2163 key: &[u8],
2164 max_nib: usize,
2165 ) -> usize {
2166 let p = 1usize;
2167 self.index
2168 .push(Some((NonZero::new(off).unwrap(), key_len, value)));
2169 let p_idx = PTR::from_usize(p);
2170 if max_nib == 0 {
2171 let mut root = Node::new();
2172 root.set_terminal(true);
2173 root.leaf = OptNz::from_index(p_idx);
2174 self.arena.push(ArenaNode::Inode(root));
2175 } else {
2176 let first_nib = key_nibble_at(key, 0) as usize;
2177 let mut root = Node::new();
2178 root.set_leaf_child(first_nib, p_idx);
2179 root.leaf = OptNz::from_index(p_idx);
2180 self.arena.push(ArenaNode::Inode(root));
2181 }
2182 p
2183 }
2184}
2185
2186// ---------------------------------------------------------------------------
2187// PTR width conversions (promote/demote)
2188// ---------------------------------------------------------------------------
2189
2190impl<K: ByteKey, T, PTR: TrieIndex, LEN: TrieIndex> NibbleTrie<K, T, PTR, LEN> {
2191 /// Promote the arena index type to a wider PTR.
2192 /// All child indices and leaf key indices are widened via `NewPTR::from_usize`.
2193 pub fn promote<NewPTR: TrieIndex>(self) -> NibbleTrie<K, T, NewPTR, LEN> {
2194 let arena = self.arena.into_iter().map(|node| node.promote()).collect();
2195 NibbleTrie {
2196 arena,
2197 buf: self.buf,
2198 index: self.index,
2199 n_keys: self.n_keys,
2200 _key: PhantomData,
2201 }
2202 }
2203
2204 /// Demote the arena index type to a narrower PTR.
2205 /// Returns `Err(self)` if any index doesn't fit in the narrower type.
2206 pub fn demote<NewPTR: TrieIndex>(self) -> Result<NibbleTrie<K, T, NewPTR, LEN>, Self> {
2207 if self.arena.len() > NewPTR::max_value() || self.index.len() > NewPTR::max_value() {
2208 return Err(self);
2209 }
2210 let arena = self.arena.into_iter().map(|node| {
2211 node.demote().expect("demote capacity check should have caught this")
2212 }).collect();
2213 Ok(NibbleTrie {
2214 arena,
2215 buf: self.buf,
2216 index: self.index,
2217 n_keys: self.n_keys,
2218 _key: PhantomData,
2219 })
2220 }
2221}
2222
2223
2224// ---------------------------------------------------------------------------
2225// Iterator
2226// ---------------------------------------------------------------------------
2227
2228/// Sentinel nib value meaning "positioned at the terminal value of this node."
2229const TERMINAL_NIB: usize = 16;
2230
2231/// A stack frame for [`NibbleIter`]. The root is always an [`Frame::Inode`]
2232/// (the root Inode); [`Frame::Fnode`] frames appear only below the root,
2233/// mirroring the "Fnodes only appear below the root" arena invariant.
2234#[derive(Clone, Copy)]
2235pub(crate) enum Frame<PTR: TrieIndex> {
2236 /// An Inode frame: `encoded` = arena index, `mask` = its child occupancy
2237 /// mask, `nib` = the current child nibble (0..16), `TERMINAL_NIB` for the
2238 /// node's own terminal, or `usize::MAX` for "parked before the first child"
2239 /// (the initial root frame).
2240 Inode { encoded: PTR, mask: u16, nib: usize },
2241 /// An Fnode frame: positioned on terminal position `pos` — `0` = `base`
2242 /// (only when the Fnode's `terminal` flag is set), `i+1` = array slot `i`
2243 /// (a non-NULL-offset slot). Pre-order (base then array slots) is sorted key
2244 /// order, so `pos` enumerates the Fnode's terminals ascending.
2245 Fnode { arena_idx: PTR, pos: usize },
2246}
2247
2248/// Internal tree-walking cursor (stack-based arena DFS). Used only for
2249/// `bump_walk`'s seek-positioning and to land the public `Cursor`'s `seek` on
2250/// a key in O(keylen). Public iteration uses the linear-scan [`Cursor`].
2251pub(crate) struct NibbleIter<'a, K: ByteKey, T, PTR: TrieIndex, LEN: TrieIndex> {
2252 trie: &'a NibbleTrie<K, T, PTR, LEN>,
2253 /// DFS stack of [`Frame`]s — `Inode` for the direct-addressed 16-slot nodes,
2254 /// `Fnode` for the flat leaf-pack nodes (a DAG leaf: walk terminals in
2255 /// slot order, skip `None` branch markers).
2256 pub(crate) stack: Vec<Frame<PTR>>,
2257}
2258
2259impl<'a, K: ByteKey, T, PTR: TrieIndex, LEN: TrieIndex> NibbleIter<'a, K, T, PTR, LEN> {
2260 fn new(trie: &'a NibbleTrie<K, T, PTR, LEN>) -> Self {
2261 if trie.arena.is_empty() {
2262 return NibbleIter { trie, stack: Vec::new() };
2263 }
2264 let mask = trie.inode(0).children_mask();
2265 let nib = if trie.inode(0).is_terminal() { TERMINAL_NIB } else { usize::MAX };
2266 NibbleIter { trie, stack: vec![Frame::Inode { encoded: PTR::zero(), mask, nib }] }
2267 }
2268
2269 fn descend_first(&mut self, mut phys_idx: usize) {
2270 loop {
2271 // Fnode? Position at its first terminal and stop (an Fnode is a
2272 // leaf). Compute the position without holding an arena borrow
2273 // across the `stack.push`.
2274 let fnode_pos = match &self.trie.arena[phys_idx] {
2275 ArenaNode::Fnode(f) => Some(
2276 f.first_terminal_pos()
2277 .expect("descend_first: Fnode with no terminals"),
2278 ),
2279 ArenaNode::Inode(_) => None,
2280 };
2281 if let Some(pos) = fnode_pos {
2282 self.stack.push(Frame::Fnode { arena_idx: PTR::from_usize(phys_idx), pos });
2283 return;
2284 }
2285 // Inode: copy out (Node: Copy) so no borrow is held across `push`.
2286 let node = *self.trie.inode(phys_idx);
2287 if node.is_terminal() {
2288 let mask = node.children_mask();
2289 self.stack.push(Frame::Inode { encoded: PTR::from_usize(phys_idx), mask, nib: TERMINAL_NIB });
2290 return;
2291 }
2292 let mask = node.children_mask();
2293 debug_assert!(mask != 0, "descend_first: non-terminal node with no children");
2294 let nib = mask.trailing_zeros() as usize;
2295 self.stack.push(Frame::Inode { encoded: PTR::from_usize(phys_idx), mask, nib });
2296 if node.is_leaf(nib) {
2297 return;
2298 }
2299 phys_idx = node.children[nib].get().as_usize();
2300 }
2301 }
2302
2303 #[inline]
2304 fn push_next_child(&mut self, encoded: PTR, mask: u16, start_nib: usize) -> bool {
2305 let shifted = if start_nib >= 16 { 0u16 } else { mask >> start_nib };
2306 if shifted == 0 {
2307 return false;
2308 }
2309 let nib = start_nib + shifted.trailing_zeros() as usize;
2310 debug_assert!(nib < 16);
2311 debug_assert!(mask & (1 << nib) != 0);
2312 let phys_idx = encoded.as_usize();
2313 // `encoded` is the parent Inode (Fnodes never have children, so a frame
2314 // passed here is always an Inode). Copy it out to release the borrow
2315 // before `push`.
2316 let node = *self.trie.inode(phys_idx);
2317 self.stack.push(Frame::Inode { encoded, mask, nib });
2318 if !node.is_leaf(nib) {
2319 let addr = node.children[nib].get().as_usize();
2320 self.descend_first(addr);
2321 }
2322 true
2323 }
2324
2325 #[inline]
2326 fn backtrack_to_next(&mut self) -> Option<(&[u8], &T)> {
2327 loop {
2328 let frame = self.stack.pop()?;
2329 match frame {
2330 Frame::Inode { encoded, mask, nib } => {
2331 if self.push_next_child(encoded, mask, nib + 1) {
2332 return self.current();
2333 }
2334 }
2335 Frame::Fnode { .. } => {
2336 // Exhausted Fnode frame left on the stack — skip it (the pop
2337 // above already consumed it) and continue backtracking.
2338 continue;
2339 }
2340 }
2341 }
2342 }
2343
2344 pub fn current(&self) -> Option<(&[u8], &T)> {
2345 let frame = self.stack.last()?;
2346 match *frame {
2347 Frame::Inode { encoded, nib, .. } => {
2348 if nib == usize::MAX {
2349 return None;
2350 }
2351 let phys_idx = encoded.as_usize();
2352 let node = self.trie.inode(phys_idx);
2353 if nib == TERMINAL_NIB {
2354 let ki = node.leaf.get();
2355 let (off, len, val) = self.trie.index[ki.as_usize()].as_ref().unwrap();
2356 let off = off.get();
2357 Some((&self.trie.buf[off..off + len.as_usize()], val))
2358 } else if let Some(key_index) = node.leaf_key_index(nib) {
2359 Some((self.trie.key_slice(key_index), &self.trie.index[key_index.as_usize()].as_ref().unwrap().2))
2360 } else {
2361 None
2362 }
2363 }
2364 Frame::Fnode { arena_idx, pos } => {
2365 let f = match &self.trie.arena[arena_idx.as_usize()] {
2366 ArenaNode::Fnode(f) => f,
2367 ArenaNode::Inode(_) => unreachable!("Fnode frame points at an Inode"),
2368 };
2369 let ki = f
2370 .pos_key_index(pos)
2371 .expect("current: Fnode frame positioned on a non-terminal");
2372 Some((self.trie.key_slice(ki), &self.trie.index[ki.as_usize()].as_ref().unwrap().2))
2373 }
2374 }
2375 }
2376
2377 pub fn current_index(&self) -> Option<usize> {
2378 let frame = self.stack.last()?;
2379 match *frame {
2380 Frame::Inode { encoded, nib, .. } => {
2381 if nib == usize::MAX {
2382 return None;
2383 }
2384 let phys_idx = encoded.as_usize();
2385 let node = self.trie.inode(phys_idx);
2386 if nib == TERMINAL_NIB {
2387 Some(node.leaf.get().as_usize())
2388 } else {
2389 node.leaf_key_index(nib).map(|ki| ki.as_usize())
2390 }
2391 }
2392 Frame::Fnode { arena_idx, pos } => {
2393 let f = match &self.trie.arena[arena_idx.as_usize()] {
2394 ArenaNode::Fnode(f) => f,
2395 ArenaNode::Inode(_) => unreachable!("Fnode frame points at an Inode"),
2396 };
2397 Some(
2398 f.pos_key_index(pos)
2399 .expect("current_index: Fnode frame positioned on a non-terminal")
2400 .as_usize(),
2401 )
2402 }
2403 }
2404 }
2405
2406 #[inline]
2407 fn advance_next(&mut self) -> bool {
2408 loop {
2409 let frame = match self.stack.pop() {
2410 Some(v) => v,
2411 None => return false,
2412 };
2413 match frame {
2414 Frame::Inode { encoded, mask, nib } => {
2415 if nib == TERMINAL_NIB {
2416 if self.push_next_child(encoded, mask, 0) {
2417 return true;
2418 }
2419 continue;
2420 }
2421 let search_start = if nib == usize::MAX { 0 } else { nib + 1 };
2422 if self.push_next_child(encoded, mask, search_start) {
2423 return true;
2424 }
2425 // `push_next_child` returned false and this frame is already
2426 // popped — loop to pop the next frame and backtrack from it.
2427 continue;
2428 }
2429 Frame::Fnode { arena_idx, pos } => {
2430 // Advance to the next terminal position in this Fnode (base
2431 // then array terminal slots). Extract the result without
2432 // holding an arena borrow across `push`.
2433 let next_pos = match &self.trie.arena[arena_idx.as_usize()] {
2434 ArenaNode::Fnode(f) => f.next_terminal_pos(pos),
2435 ArenaNode::Inode(_) => unreachable!("Fnode frame points at an Inode"),
2436 };
2437 if let Some(np) = next_pos {
2438 self.stack.push(Frame::Fnode { arena_idx, pos: np });
2439 return true;
2440 }
2441 // Exhausted Fnode: this frame is already popped — loop to
2442 // pop the parent Inode frame and backtrack from there.
2443 continue;
2444 }
2445 }
2446 }
2447 }
2448
2449 #[inline]
2450 pub fn next(&mut self) -> Option<(&[u8], &T)> {
2451 if self.advance_next() { self.current() } else { None }
2452 }
2453
2454 /// Seek within an Fnode child for the first terminal key ≥ `key`. The parent
2455 /// Inode frame is already on the stack (pushed by [`seek`](Self::seek) before
2456 /// dispatching here). On a hit, push an [`Frame::Fnode`] and return `current`.
2457 /// On exhaust (all Fnode terminals < `key`), pop the parent and backtrack to
2458 /// its next child.
2459 fn fnode_seek(&mut self, arena_idx: usize, key: &[u8], _max_nib: usize) -> Option<(&[u8], &T)> {
2460 // Pre-order (base then array slots) == sorted key order: the first
2461 // terminal whose key is ≥ `key` is the lower bound. Scan inside a block
2462 // so the arena borrow ends before the `stack.push` below.
2463 let found_pos: Option<usize> = {
2464 let f = match &self.trie.arena[arena_idx] {
2465 ArenaNode::Fnode(f) => f,
2466 ArenaNode::Inode(_) => unreachable!("fnode_seek on an Inode"),
2467 };
2468 // `base` first (if it is a terminal).
2469 if f.terminal && self.trie.key_slice(f.base) >= key {
2470 Some(0)
2471 } else {
2472 // Array terminal slots in pre-order (ascending key order).
2473 let slots = f.slots.as_slice();
2474 let base = f.base.as_usize();
2475 let mut found = None;
2476 for (i, (_plen, offset)) in slots.iter().enumerate() {
2477 if *offset == FNODE_OFFSET_NULL {
2478 continue;
2479 }
2480 let ki = PTR::from_usize(base + *offset as usize);
2481 if self.trie.key_slice(ki) >= key {
2482 found = Some(i + 1);
2483 break;
2484 }
2485 }
2486 found
2487 }
2488 };
2489 if let Some(pos) = found_pos {
2490 self.stack.push(Frame::Fnode { arena_idx: PTR::from_usize(arena_idx), pos });
2491 return self.current();
2492 }
2493 // All Fnode terminals < key → backtrack to the parent Inode's next child.
2494 match self.stack.pop() {
2495 Some(Frame::Inode { encoded, mask, nib }) => {
2496 if self.push_next_child(encoded, mask, nib + 1) {
2497 return self.current();
2498 }
2499 self.backtrack_to_next()
2500 }
2501 other => {
2502 // The root is never an Fnode and `seek` always pushes the parent
2503 // Inode frame before dispatching, so there must be one. Defensively
2504 // restore anything unexpected and report no match.
2505 if let Some(frm) = other {
2506 self.stack.push(frm);
2507 }
2508 None
2509 }
2510 }
2511 }
2512
2513 pub fn seek(&mut self, key: &[u8]) -> Option<(&[u8], &T)> {
2514 if self.trie.arena.is_empty() {
2515 self.stack.clear();
2516 return None;
2517 }
2518
2519 self.stack.clear();
2520 let mut phys_idx: usize = 0;
2521 let max_nib = key.len() * 2;
2522
2523 loop {
2524 // Root and every ephemeral-descent target are Inodes — Fnode children
2525 // are dispatched below and `return` before re-looping. Copy the node
2526 // out (Node: Copy) so no borrow is held across `stack.push` /
2527 // recursive `self.` calls.
2528 let node = *self.trie.inode(phys_idx);
2529 let mask = node.children_mask();
2530
2531 if node.is_terminal() && node.prefix_len.as_usize() >= max_nib {
2532 let ki = node.leaf.get();
2533 let (off, len, _) = self.trie.index[ki.as_usize()].as_ref().unwrap();
2534 let off = off.get();
2535 let node_key = &self.trie.buf[off..off + len.as_usize()];
2536 if node_key >= key {
2537 self.stack.push(Frame::Inode { encoded: PTR::from_usize(phys_idx), mask, nib: TERMINAL_NIB });
2538 return self.current();
2539 }
2540 }
2541
2542 if node.prefix_len.as_usize() >= max_nib {
2543 if self.push_next_child(PTR::from_usize(phys_idx), mask, 0) {
2544 return self.current();
2545 }
2546 return self.backtrack_to_next();
2547 }
2548
2549 let nib = key_nibble_at(key, node.prefix_len.as_usize()) as usize;
2550 if !node.is_occupied(nib) {
2551 // No child at this nibble — find next higher child, or backtrack
2552 if self.push_next_child(PTR::from_usize(phys_idx), mask, nib + 1) {
2553 return self.current();
2554 }
2555 return self.backtrack_to_next();
2556 }
2557
2558 self.stack.push(Frame::Inode { encoded: PTR::from_usize(phys_idx), mask, nib });
2559 if node.is_leaf(nib) {
2560 let leaf_key = self.trie.key_slice(node.children[nib].get());
2561 if leaf_key >= key {
2562 return self.current();
2563 }
2564 // Leaf key < seek key: advance past it
2565 return self.next();
2566 } else {
2567 let child_addr = node.children[nib].get().as_usize();
2568 if matches!(self.trie.arena[child_addr], ArenaNode::Fnode(_)) {
2569 // Parent Inode frame already pushed above. Flat-seek the
2570 // Fnode: either land on a terminal ≥ key, or backtrack out.
2571 return self.fnode_seek(child_addr, key, max_nib);
2572 }
2573 phys_idx = child_addr;
2574 }
2575 }
2576 }
2577}
2578
2579// ---------------------------------------------------------------------------
2580// Cursor — public linear-scan iterator over the sparse `index`
2581// ---------------------------------------------------------------------------
2582
2583/// Public iteration cursor over a [`NibbleTrie`]: a linear scan of the sparse
2584/// `index`, skipping `None` gaps. This is correct because the index is kept
2585/// sorted by invariant — occupied slots appear in non-decreasing key order
2586/// (enforced by the Stage B shift-and-bump insert, and checked by the
2587/// invariant-oracle tests).
2588///
2589/// `iter()` parks *before* the first key (`current()` is `None`, `next()`
2590/// yields the first key — the idiomatic `Iterator` model); `iter_last()` parks
2591/// *on* the last key (`current()` returns it, `prev()` walks backward). `seek`
2592/// lands in O(keylen) via the internal tree walker, then `next`/`prev` resume
2593/// the linear scan. `first`/`last` jump to the ends. The current key/value is
2594/// cached at park time, so `current()` (and a `next().current()` follow-up) is
2595/// a pure field read with no re-scan.
2596///
2597/// The cached refs borrow the trie (lifetime `'a`), not the cursor, so the
2598/// `&'a T` returned by `current`/`next`/`prev`/`seek` outlives the cursor
2599/// borrow. The key is returned as [`ByteKey::Borrowed<'a>`] (via
2600/// [`ByteKey::as_borrowed`]) — a zero-allocation view into the trie's key
2601/// buffer (`&'a [u8]` for `Vec<u8>` keys, `&'a str` for
2602/// `String` keys). The slice is cached internally, so `current()`/`next()` pay
2603/// only the `as_borrowed` view (no allocation, no re-scan).
2604pub struct Cursor<'a, K: ByteKey, T, PTR: TrieIndex, LEN: TrieIndex> {
2605 trie: &'a NibbleTrie<K, T, PTR, LEN>,
2606 /// Slot index parked on, or a sentinel: `0` = before-first / backward
2607 /// exhausted (slot 0 is the dummy `None`), `index.len()` = forward
2608 /// exhausted. A parked `pos` is always a `Some` slot in `[1, len-1]`.
2609 pos: usize,
2610 /// Cached `current()` value: `Some` iff `pos` is a `Some` slot.
2611 cur: Option<(&'a [u8], &'a T)>,
2612}
2613
2614impl<'a, K: ByteKey, T, PTR: TrieIndex, LEN: TrieIndex> Cursor<'a, K, T, PTR, LEN> {
2615 /// Park at a known-occupied slot, building the cached current value
2616 /// directly from the already-fetched `slot` ref. `slot` borrows the trie
2617 /// with lifetime `'a` (so the cached refs carry `'a`, not the cursor
2618 /// borrow) — safe to then write `self.cur`. The caller has already proven
2619 /// the slot is `Some`, so this is branch-free apart from the slice bounds.
2620 #[inline]
2621 fn park_slot(&mut self, pos: usize, slot: &'a Slot<LEN, T>) {
2622 self.pos = pos;
2623 let off = slot.0.get();
2624 let klen = slot.1.as_usize();
2625 self.cur = Some((&self.trie.buf[off..off + klen], &slot.2));
2626 }
2627
2628 /// Park at a sentinel (`0` = before-first / backward exhausted, or `len` =
2629 /// forward exhausted): no live key, so the cached current is `None`.
2630 #[inline]
2631 fn park_sentinel(&mut self, pos: usize) {
2632 self.pos = pos;
2633 self.cur = None;
2634 }
2635
2636 /// Forward cursor parked *before* the first key.
2637 pub fn new(trie: &'a NibbleTrie<K, T, PTR, LEN>) -> Self {
2638 Cursor { trie, pos: 0, cur: None }
2639 }
2640
2641 /// Reverse cursor parked *on* the last key (or before-first if empty).
2642 pub fn new_last(trie: &'a NibbleTrie<K, T, PTR, LEN>) -> Self {
2643 let mut c = Cursor { trie, pos: 0, cur: None };
2644 c.last();
2645 c
2646 }
2647
2648 /// Jump to the first key (smallest slot). Returns its key/value, or `None`
2649 /// if the trie is empty. Scans forward from slot 1.
2650 pub fn first(&mut self) -> Option<(K::Borrowed<'a>, &'a T)> {
2651 let len = self.trie.index.len();
2652 let mut i = 1;
2653 while i < len {
2654 if let Some(slot) = self.trie.index[i].as_ref() {
2655 self.park_slot(i, slot);
2656 return self.cur.map(|(k, v)| (K::as_borrowed(k), v));
2657 }
2658 i += 1;
2659 }
2660 self.park_sentinel(0);
2661 None
2662 }
2663
2664 /// Jump to the last key (largest slot). Returns its key/value, or `None` if
2665 /// the trie is empty. Scans backward from the end of `index`.
2666 pub fn last(&mut self) -> Option<(K::Borrowed<'a>, &'a T)> {
2667 let mut i = self.trie.index.len();
2668 while i > 1 {
2669 i -= 1;
2670 if let Some(slot) = self.trie.index[i].as_ref() {
2671 self.park_slot(i, slot);
2672 return self.cur.map(|(k, v)| (K::as_borrowed(k), v));
2673 }
2674 }
2675 self.park_sentinel(0);
2676 None
2677 }
2678
2679 /// The key/value the cursor is parked on, or `None` if not parked (before
2680 /// first, or exhausted). A pure field read — the slice/value pair is cached
2681 /// by `park`; only the zero-alloc `as_borrowed` view runs per call.
2682 #[inline]
2683 pub fn current(&self) -> Option<(K::Borrowed<'a>, &'a T)> {
2684 self.cur.map(|(k, v)| (K::as_borrowed(k), v))
2685 }
2686
2687 /// The slot index the cursor is parked on, or `None` if not parked.
2688 #[inline]
2689 pub fn current_index(&self) -> Option<usize> {
2690 if self.cur.is_some() { Some(self.pos) } else { None }
2691 }
2692
2693 /// Advance to the next occupied slot and return its key/value. Returns
2694 /// `None` (parking at the forward-exhausted sentinel) when no further key
2695 /// exists.
2696 #[inline]
2697 pub fn next(&mut self) -> Option<(K::Borrowed<'a>, &'a T)> {
2698 if self.advance_next() {
2699 self.cur.map(|(k, v)| (K::as_borrowed(k), v))
2700 } else {
2701 None
2702 }
2703 }
2704
2705 /// Step to the previous occupied slot and return its key/value. Returns
2706 /// `None` (parking at the before-first sentinel) when no prior key exists.
2707 #[inline]
2708 pub fn prev(&mut self) -> Option<(K::Borrowed<'a>, &'a T)> {
2709 if self.advance_prev() {
2710 self.cur.map(|(k, v)| (K::as_borrowed(k), v))
2711 } else {
2712 None
2713 }
2714 }
2715
2716 #[inline]
2717 pub fn next_index(&mut self) -> Option<usize> {
2718 if self.advance_next() { Some(self.pos) } else { None }
2719 }
2720
2721 #[inline]
2722 pub fn prev_index(&mut self) -> Option<usize> {
2723 if self.advance_prev() { Some(self.pos) } else { None }
2724 }
2725
2726 /// Land on the first key ≥ `key` — O(keylen) via the internal tree walker —
2727 /// then return its key/value. Returns `None` if no key is ≥ `key`.
2728 pub fn seek(&mut self, key: &[u8]) -> Option<(K::Borrowed<'a>, &'a T)> {
2729 let pos = {
2730 let mut w = self.trie.walk_iter();
2731 w.seek(key);
2732 w.current_index()
2733 };
2734 match pos {
2735 Some(p) => {
2736 // `p` is a tree-walker-confirmed occupied slot.
2737 if let Some(slot) = self.trie.index[p].as_ref() {
2738 self.park_slot(p, slot);
2739 self.cur.map(|(k, v)| (K::as_borrowed(k), v))
2740 } else {
2741 self.park_sentinel(self.trie.index.len());
2742 None
2743 }
2744 }
2745 None => { self.park_sentinel(self.trie.index.len()); None }
2746 }
2747 }
2748
2749 // --- core linear scans ---
2750
2751 /// Scan forward from `pos+1` to the next `Some` slot; park there on hit,
2752 /// or at the `len` sentinel on miss. Each slot is fetched once and, on a
2753 /// hit, handed straight to `park_slot` — no second `index` load and no
2754 /// re-match of the `Option` (which the old `park` did).
2755 #[inline]
2756 fn advance_next(&mut self) -> bool {
2757 let len = self.trie.index.len();
2758 let mut i = self.pos + 1;
2759 while i < len {
2760 if let Some(slot) = self.trie.index[i].as_ref() {
2761 self.park_slot(i, slot);
2762 return true;
2763 }
2764 i += 1;
2765 }
2766 self.park_sentinel(len);
2767 false
2768 }
2769
2770 /// Scan backward from `pos-1` to the previous `Some` slot; park there on
2771 /// hit, or at the `0` (before-first) sentinel on miss. Same single-fetch
2772 /// strategy as `advance_next`.
2773 #[inline]
2774 fn advance_prev(&mut self) -> bool {
2775 let mut i = self.pos;
2776 while i > 1 {
2777 i -= 1;
2778 if let Some(slot) = self.trie.index[i].as_ref() {
2779 self.park_slot(i, slot);
2780 return true;
2781 }
2782 }
2783 self.park_sentinel(0);
2784 false
2785 }
2786}
2787
2788// ---------------------------------------------------------------------------
2789// CursorMut — public linear-scan iterator lending out &mut T
2790// ---------------------------------------------------------------------------
2791
2792/// Mutable counterpart to [`Cursor`]: a linear scan of the sparse `index`
2793/// that lends out `&mut T` borrows over the stored values.
2794///
2795/// Unlike [`Cursor`], the value reference is tied to `&mut self` (a *lending*
2796/// cursor), not to the trie lifetime `'a`. This is a soundness requirement, not
2797/// a stylistic choice: a cursor is re-positionable — `current()`, `seek()`,
2798/// `first()`, `last()` can all revisit a slot already visited. An `'a`-tied
2799/// `&mut T` (as the immutable cursor hands out `&'a T`) would let two such
2800/// calls return `&'a mut T` to the *same* element simultaneously — aliasing
2801/// undefined behavior. Tying the borrow to `&mut self` makes the borrow checker
2802/// enforce "one live `&mut T` at a time," which is the only sound rule for a
2803/// re-positionable mutable cursor. The practical consequence: you cannot
2804/// collect the `&mut T` into a `Vec` or hold two at once; each must be released
2805/// before the next `next()`/`prev()`/`current()`/`seek()` call. In-place
2806/// mutation loops (`while let Some((k, v)) = c.next() { *v += 1; }`) work as
2807/// expected.
2808///
2809/// The key is returned as [`ByteKey::Borrowed<'_>`] (via [`ByteKey::as_borrowed`])
2810/// — a zero-alloc view into the trie's key buffer, tied to the same `&mut self`
2811/// borrow as the `&mut T` (so it, too, must be released before the next call).
2812/// Only the stored *value* is mutated; the cursor never alters key bytes, node
2813/// structure, or slot occupancy, so trie invariants are preserved.
2814pub struct CursorMut<'a, K: ByteKey, T, PTR: TrieIndex, LEN: TrieIndex> {
2815 trie: &'a mut NibbleTrie<K, T, PTR, LEN>,
2816 /// Slot index parked on, or a sentinel: `0` = before-first / backward
2817 /// exhausted (slot 0 is the dummy `None`), `index.len()` = forward
2818 /// exhausted. A parked `pos` is always a `Some` slot in `[1, len-1]`.
2819 pos: usize,
2820}
2821
2822impl<'a, K: ByteKey, T, PTR: TrieIndex, LEN: TrieIndex> CursorMut<'a, K, T, PTR, LEN> {
2823 /// Forward mutable cursor parked *before* the first key.
2824 pub fn new(trie: &'a mut NibbleTrie<K, T, PTR, LEN>) -> Self {
2825 CursorMut { trie, pos: 0 }
2826 }
2827
2828 /// Reverse mutable cursor parked *on* the last key (or before-first if
2829 /// empty).
2830 pub fn new_last(trie: &'a mut NibbleTrie<K, T, PTR, LEN>) -> Self {
2831 let mut c = CursorMut { trie, pos: 0 };
2832 c.last();
2833 c
2834 }
2835
2836 /// Build the `(K::Borrowed<'_>, &mut T)` pair for the slot at `self.pos`.
2837 /// The `pos` must be a parked, occupied slot. Three sequential borrows that
2838 /// the borrow checker sees as disjoint fields of `*self.trie`: (1) immutable
2839 /// peek of the slot for `off`/`len` (copied out as `usize`, borrow ends),
2840 /// (2) immutable read of `buf` for the borrowed key view (held for `'b` in
2841 /// the return), (3) mutable borrow of the slot for `&mut T` (held for `'b`).
2842 /// The `buf` (shared) and `index` (mutable) borrows coexist on disjoint
2843 /// fields. Both are tied to `&mut self` — the lending contract in the type
2844 /// docs.
2845 #[inline]
2846 fn materialize<'b>(&'b mut self) -> Option<(K::Borrowed<'b>, &'b mut T)> {
2847 let pos = self.pos;
2848 let (off, len) = {
2849 let slot = self.trie.index[pos].as_ref()?;
2850 (slot.0.get(), slot.1.as_usize())
2851 };
2852 let k = K::as_borrowed(&self.trie.buf[off..off + len]);
2853 let slot = self.trie.index[pos].as_mut()?;
2854 Some((k, &mut slot.2))
2855 }
2856
2857 /// Jump to the first key (smallest slot). Returns its key/value, or `None`
2858 /// if the trie is empty. Scans forward from slot 1.
2859 pub fn first(&mut self) -> Option<(K::Borrowed<'_>, &mut T)> {
2860 self.pos = 0;
2861 if self.advance_next() { self.materialize() } else { None }
2862 }
2863
2864 /// Jump to the last key (largest slot). Returns its key/value, or `None` if
2865 /// the trie is empty. Scans backward from the end of `index`.
2866 pub fn last(&mut self) -> Option<(K::Borrowed<'_>, &mut T)> {
2867 self.pos = self.trie.index.len();
2868 if self.advance_prev() { self.materialize() } else { None }
2869 }
2870
2871 /// The key/value the cursor is parked on, or `None` if not parked (before
2872 /// first, or exhausted). Reconstructs `K` and reborrows `&mut T` per call.
2873 #[inline]
2874 pub fn current(&mut self) -> Option<(K::Borrowed<'_>, &mut T)> {
2875 let len = self.trie.index.len();
2876 if self.pos == 0 || self.pos >= len {
2877 return None;
2878 }
2879 self.materialize()
2880 }
2881
2882 /// The slot index the cursor is parked on, or `None` if not parked.
2883 #[inline]
2884 pub fn current_index(&self) -> Option<usize> {
2885 let len = self.trie.index.len();
2886 if self.pos != 0 && self.pos < len { Some(self.pos) } else { None }
2887 }
2888
2889 /// Advance to the next occupied slot and return its key/value. Returns
2890 /// `None` (parking at the forward-exhausted sentinel) when no further key
2891 /// exists.
2892 #[inline]
2893 pub fn next(&mut self) -> Option<(K::Borrowed<'_>, &mut T)> {
2894 if self.advance_next() { self.materialize() } else { None }
2895 }
2896
2897 /// Step to the previous occupied slot and return its key/value. Returns
2898 /// `None` (parking at the before-first sentinel) when no prior key exists.
2899 #[inline]
2900 pub fn prev(&mut self) -> Option<(K::Borrowed<'_>, &mut T)> {
2901 if self.advance_prev() { self.materialize() } else { None }
2902 }
2903
2904 #[inline]
2905 pub fn next_index(&mut self) -> Option<usize> {
2906 if self.advance_next() { Some(self.pos) } else { None }
2907 }
2908
2909 #[inline]
2910 pub fn prev_index(&mut self) -> Option<usize> {
2911 if self.advance_prev() { Some(self.pos) } else { None }
2912 }
2913
2914 /// Land on the first key ≥ `key` — O(keylen) via the internal tree walker —
2915 /// then return its key/value. Returns `None` if no key is ≥ `key`.
2916 pub fn seek(&mut self, key: &[u8]) -> Option<(K::Borrowed<'_>, &mut T)> {
2917 let pos = {
2918 let trie = &*self.trie;
2919 let mut w = trie.walk_iter();
2920 w.seek(key);
2921 w.current_index()
2922 };
2923 let len = self.trie.index.len();
2924 match pos {
2925 Some(p) if self.trie.index[p].is_some() => {
2926 self.pos = p;
2927 self.materialize()
2928 }
2929 _ => { self.pos = len; None }
2930 }
2931 }
2932
2933 // --- core linear scans (position only; no borrow handed out) ---
2934
2935 /// Scan forward from `pos+1` to the next `Some` slot; park there on hit,
2936 /// or at the `len` sentinel on miss. Only updates `pos` — no value borrow
2937 /// is taken, so the caller can then `materialize` a fresh `&mut T`.
2938 #[inline]
2939 fn advance_next(&mut self) -> bool {
2940 let len = self.trie.index.len();
2941 let mut i = self.pos + 1;
2942 while i < len {
2943 if self.trie.index[i].is_some() {
2944 self.pos = i;
2945 return true;
2946 }
2947 i += 1;
2948 }
2949 self.pos = len;
2950 false
2951 }
2952
2953 /// Scan backward from `pos-1` to the previous `Some` slot; park there on
2954 /// hit, or at the `0` sentinel on miss. Only updates `pos`.
2955 #[inline]
2956 fn advance_prev(&mut self) -> bool {
2957 let mut i = self.pos;
2958 while i > 1 {
2959 i -= 1;
2960 if self.trie.index[i].is_some() {
2961 self.pos = i;
2962 return true;
2963 }
2964 }
2965 self.pos = 0;
2966 false
2967 }
2968}
2969
2970// ---------------------------------------------------------------------------
2971// Range — zero-alloc ascending iterator over a key interval
2972// ---------------------------------------------------------------------------
2973
2974/// Ascending iterator over a half-open key interval of a [`NibbleTrie`],
2975/// yielding `(K::Borrowed<'a>, &'a T)` with no allocation.
2976///
2977/// Constructed via [`NibbleTrie::range`]. Both bounds are resolved to slot
2978/// indices with O(keylen) seeks at construction time; iteration is then a
2979/// linear scan of the sparse `index` bounded by `pos < end_pos` (a `usize`
2980/// compare), so no per-element key comparison runs. `None` gaps between
2981/// `start_pos` and `end_pos` are skipped. The item borrows the trie (`'a`), not
2982/// the iterator, so [`Iterator`] is implemented directly (not lending).
2983///
2984/// Bound semantics match `BTreeMap::range`:
2985/// - `Included(k)` lower → first key ≥ `k`; upper → include keys ≤ `k`.
2986/// - `Excluded(k)` lower → first key > `k`; upper → include keys < `k`.
2987/// - `Unbounded` lower → first key; upper → last key.
2988pub struct Range<'a, K: ByteKey, T, PTR: TrieIndex, LEN: TrieIndex> {
2989 trie: &'a NibbleTrie<K, T, PTR, LEN>,
2990 /// Next slot index to scan from. `0` = before-first; `end_pos` = exhausted.
2991 pos: usize,
2992 /// Exclusive upper slot bound: yield occupied slots with index `< end_pos`.
2993 end_pos: usize,
2994}
2995
2996impl<'a, K: ByteKey, T, PTR: TrieIndex, LEN: TrieIndex> Range<'a, K, T, PTR, LEN> {
2997 /// Build a `Range` from `(start, end)` bounds. Each concrete bound costs one
2998 /// O(keylen) seek; `Unbounded` bounds are free.
2999 pub(crate) fn new(
3000 trie: &'a NibbleTrie<K, T, PTR, LEN>,
3001 start: Bound<&[u8]>,
3002 end: Bound<&[u8]>,
3003 ) -> Self {
3004 let len = trie.index.len();
3005 // Lower bound → first slot to yield.
3006 let pos = match start {
3007 Bound::Included(k) => ceiling_index(trie, k).unwrap_or(len),
3008 Bound::Excluded(k) => ceiling_strict_index(trie, k).unwrap_or(len),
3009 Bound::Unbounded => 0, // slot 0 is the dummy None; scan skips it.
3010 };
3011 // Upper bound → exclusive index of the first key to exclude.
3012 let end_pos = match end {
3013 Bound::Included(k) => ceiling_strict_index(trie, k).unwrap_or(len),
3014 Bound::Excluded(k) => ceiling_index(trie, k).unwrap_or(len),
3015 Bound::Unbounded => len,
3016 };
3017 Range { trie, pos, end_pos }
3018 }
3019}
3020
3021impl<'a, K: ByteKey, T, PTR: TrieIndex, LEN: TrieIndex> Iterator for Range<'a, K, T, PTR, LEN> {
3022 type Item = (K::Borrowed<'a>, &'a T);
3023
3024 #[inline]
3025 fn next(&mut self) -> Option<Self::Item> {
3026 let end = self.end_pos;
3027 let mut i = self.pos;
3028 while i < end {
3029 if let Some(slot) = self.trie.index[i].as_ref() {
3030 let off = slot.0.get();
3031 let klen = slot.1.as_usize();
3032 let k = K::as_borrowed(&self.trie.buf[off..off + klen]);
3033 self.pos = i + 1;
3034 return Some((k, &slot.2));
3035 }
3036 i += 1;
3037 }
3038 self.pos = end;
3039 None
3040 }
3041
3042 #[inline]
3043 fn size_hint(&self) -> (usize, Option<usize>) {
3044 // Upper bound: at most `end_pos - pos` slots (gaps reduce the true
3045 // count). A precise count would require scanning, which defeats the
3046 // point, so report only the loose upper bound.
3047 let remaining = self.end_pos.saturating_sub(self.pos);
3048 (0, Some(remaining))
3049 }
3050}
3051
3052// `Range::next` only reads `index` and `buf` through the shared `&'a NibbleTrie`
3053// borrow, so it is safe to hand out items that outlive the `&mut self` of
3054// `next` — hence a true `Iterator`, not a lending one.
3055impl<'a, K: ByteKey, T, PTR: TrieIndex, LEN: TrieIndex> DoubleEndedIterator
3056 for Range<'a, K, T, PTR, LEN>
3057{
3058 /// Walk backward from `end_pos - 1` to `pos`, yielding the largest occupied
3059 /// slot still in range. `next_back` and `next` stay consistent because both
3060 /// close in on the same `[pos, end_pos)` span.
3061 #[inline]
3062 fn next_back(&mut self) -> Option<Self::Item> {
3063 let start = self.pos;
3064 let mut i = self.end_pos;
3065 while i > start {
3066 i -= 1;
3067 if let Some(slot) = self.trie.index[i].as_ref() {
3068 let off = slot.0.get();
3069 let klen = slot.1.as_usize();
3070 let k = K::as_borrowed(&self.trie.buf[off..off + klen]);
3071 self.end_pos = i;
3072 return Some((k, &slot.2));
3073 }
3074 }
3075 self.end_pos = start;
3076 None
3077 }
3078}
3079
3080/// Slot index of the first occupied slot with key ≥ `key` (the ceiling), via
3081/// the O(keylen) tree walker. `None` if no key is ≥ `key`.
3082fn ceiling_index<K: ByteKey, T, PTR: TrieIndex, LEN: TrieIndex>(
3083 trie: &NibbleTrie<K, T, PTR, LEN>,
3084 key: &[u8],
3085) -> Option<usize> {
3086 let mut w = trie.walk_iter();
3087 w.seek(key);
3088 w.current_index()
3089}
3090
3091/// Slot index of the first occupied slot with key strictly > `key`. Seeks to the
3092/// ceiling of `key`; if that slot's key equals `key`, advances to the next
3093/// occupied slot. `None` if no such key exists.
3094fn ceiling_strict_index<K: ByteKey, T, PTR: TrieIndex, LEN: TrieIndex>(
3095 trie: &NibbleTrie<K, T, PTR, LEN>,
3096 key: &[u8],
3097) -> Option<usize> {
3098 let p = ceiling_index(trie, key)?;
3099 let slot = trie.index[p].as_ref()?;
3100 let off = slot.0.get();
3101 let klen = slot.1.as_usize();
3102 if &trie.buf[off..off + klen] == key {
3103 // The ceiling is `key` itself; the strict ceiling is the next occupied
3104 // slot after it.
3105 let len = trie.index.len();
3106 let mut i = p + 1;
3107 while i < len {
3108 if trie.index[i].is_some() {
3109 return Some(i);
3110 }
3111 i += 1;
3112 }
3113 None
3114 } else {
3115 Some(p)
3116 }
3117}
3118
3119#[cfg(test)]
3120#[path = "tests/nibble_trie.rs"]
3121mod tests;