Skip to main content

tiny_trie/
dyn_trie.rs

1//! Dynamic NibbleTrie — starts with compact u8 arena indices and promotes
2//! to wider types (u16 → u32 → u64) as the trie grows.
3//!
4//! `DynTrie<T>` wraps an enum over concrete `NibbleTrie<Vec<u8>, T, PTR, u16>`
5//! variants. On insert, it checks whether the current PTR type is approaching
6//! capacity and promotes automatically. This gives small tries the memory
7//! efficiency of u8 indices (32-byte nodes) while supporting unbounded growth.
8//!
9//! # Dispatch
10//!
11//! All method calls dispatch through a `match` on the enum variant — no vtable,
12//! no `Box`, no heap allocation on promotion. The compiler can inline concrete
13//! `NibbleTrie` methods per variant.
14
15use crate::nibble_trie::NibbleTrie;
16
17// ---------------------------------------------------------------------------
18// DynInner enum — replaces Box<dyn DynTrie<T>>
19// ---------------------------------------------------------------------------
20
21/// Internal enum holding one of the four concrete `NibbleTrie` variants.
22/// Promotion replaces the variant in-place via `std::mem::replace`.
23enum DynInner<T> {
24    U8 (NibbleTrie<Vec<u8>, T, u8,  u16>),
25    U16(NibbleTrie<Vec<u8>, T, u16, u16>),
26    U32(NibbleTrie<Vec<u8>, T, u32, u16>),
27    U64(NibbleTrie<Vec<u8>, T, u64, u16>),
28}
29
30// ---------------------------------------------------------------------------
31// DynTrie
32// ---------------------------------------------------------------------------
33
34/// A nibble trie that starts with compact u8 arena indices and automatically
35/// promotes to u16/u32/u64 as needed.
36///
37/// Node sizes by PTR width:
38/// - u8:  32 bytes (fits half a cache line)
39/// - u16: 48 bytes (fits a cache line)
40/// - u32: 80 bytes
41/// - u64: 152 bytes
42///
43/// Promotion is transparent: insert checks capacity and promotes before the
44/// underlying trie overflows. No vtable dispatch or heap allocation on
45/// promotion — just an enum variant swap.
46pub struct DynTrie<T> {
47    inner: DynInner<T>,
48}
49
50impl<T> DynTrie<T> {
51    /// Create an empty DynTrie starting with u8 arena indices (32-byte nodes).
52    pub fn new() -> Self {
53        DynTrie {
54            inner: DynInner::U8(NibbleTrie::new()),
55        }
56    }
57
58    /// Look up a key. Returns the value if found.
59    pub fn get(&self, key: &[u8]) -> Option<&T> {
60        match &self.inner {
61            DynInner::U8(t) => t.get(key),
62            DynInner::U16(t) => t.get(key),
63            DynInner::U32(t) => t.get(key),
64            DynInner::U64(t) => t.get(key),
65        }
66    }
67
68    /// Look up a key's value for mutation. Returns the value if found.
69    pub fn get_mut(&mut self, key: &[u8]) -> Option<&mut T> {
70        match &mut self.inner {
71            DynInner::U8(t) => t.get_mut(key),
72            DynInner::U16(t) => t.get_mut(key),
73            DynInner::U32(t) => t.get_mut(key),
74            DynInner::U64(t) => t.get_mut(key),
75        }
76    }
77
78    /// Insert a key-value pair. Automatically promotes to a wider PTR type if
79    /// the current one is approaching capacity. Returns the key index on
80    /// success, or `Err(())` on duplicate key.
81    pub fn insert(&mut self, key: Vec<u8>, value: T) -> Result<usize, ()> {
82        // Promote while at capacity (handles chained promotion, e.g. u8→u16→u32
83        // if the capacity threshold is overshot).
84        loop {
85            let near = match &self.inner {
86                DynInner::U8(t) => t.near_capacity(),
87                DynInner::U16(t) => t.near_capacity(),
88                DynInner::U32(t) => t.near_capacity(),
89                DynInner::U64(t) => t.near_capacity(),
90            };
91            if !near {
92                break;
93            }
94            self.promote_inner();
95        }
96        match &mut self.inner {
97            DynInner::U8(t) => t.insert(key, value),
98            DynInner::U16(t) => t.insert(key, value),
99            DynInner::U32(t) => t.insert(key, value),
100            DynInner::U64(t) => t.insert(key, value),
101        }
102    }
103
104    /// Number of entries in the trie.
105    pub fn len(&self) -> usize {
106        match &self.inner {
107            DynInner::U8(t) => t.len(),
108            DynInner::U16(t) => t.len(),
109            DynInner::U32(t) => t.len(),
110            DynInner::U64(t) => t.len(),
111        }
112    }
113
114    /// Returns `true` if the trie is empty.
115    pub fn is_empty(&self) -> bool {
116        match &self.inner {
117            DynInner::U8(t) => t.is_empty(),
118            DynInner::U16(t) => t.is_empty(),
119            DynInner::U32(t) => t.is_empty(),
120            DynInner::U64(t) => t.is_empty(),
121        }
122    }
123
124    /// Optimize the trie's memory layout for cache locality.
125    pub fn optimize(&mut self) {
126        match &mut self.inner {
127            DynInner::U8(t) => t.optimize(),
128            DynInner::U16(t) => t.optimize(),
129            DynInner::U32(t) => t.optimize(),
130            DynInner::U64(t) => t.optimize(),
131        }
132    }
133
134    /// Returns the size of the current PTR type in bytes (1, 2, 4, or 8).
135    /// Useful for debugging and testing promotion.
136    pub fn ptr_size(&self) -> usize {
137        match &self.inner {
138            DynInner::U8(_) => 1,
139            DynInner::U16(_) => 2,
140            DynInner::U32(_) => 4,
141            DynInner::U64(_) => 8,
142        }
143    }
144
145    /// Iterate all key-value pairs in forward (ascending) order.
146    pub fn iter_fwd(&self, f: &mut dyn FnMut(&[u8], &T)) {
147        match &self.inner {
148            DynInner::U8(t) => {
149                let mut it = t.iter();
150                if let Some((k, v)) = it.current() { f(k, v); }
151                while let Some((k, v)) = it.next() { f(k, v); }
152            }
153            DynInner::U16(t) => {
154                let mut it = t.iter();
155                if let Some((k, v)) = it.current() { f(k, v); }
156                while let Some((k, v)) = it.next() { f(k, v); }
157            }
158            DynInner::U32(t) => {
159                let mut it = t.iter();
160                if let Some((k, v)) = it.current() { f(k, v); }
161                while let Some((k, v)) = it.next() { f(k, v); }
162            }
163            DynInner::U64(t) => {
164                let mut it = t.iter();
165                if let Some((k, v)) = it.current() { f(k, v); }
166                while let Some((k, v)) = it.next() { f(k, v); }
167            }
168        }
169    }
170
171    /// Iterate all key-value pairs in reverse (descending) order.
172    pub fn iter_rev(&self, f: &mut dyn FnMut(&[u8], &T)) {
173        match &self.inner {
174            DynInner::U8(t) => {
175                let mut it = t.iter_last();
176                if let Some((k, v)) = it.current() { f(k, v); }
177                while let Some((k, v)) = it.prev() { f(k, v); }
178            }
179            DynInner::U16(t) => {
180                let mut it = t.iter_last();
181                if let Some((k, v)) = it.current() { f(k, v); }
182                while let Some((k, v)) = it.prev() { f(k, v); }
183            }
184            DynInner::U32(t) => {
185                let mut it = t.iter_last();
186                if let Some((k, v)) = it.current() { f(k, v); }
187                while let Some((k, v)) = it.prev() { f(k, v); }
188            }
189            DynInner::U64(t) => {
190                let mut it = t.iter_last();
191                if let Some((k, v)) = it.current() { f(k, v); }
192                while let Some((k, v)) = it.prev() { f(k, v); }
193            }
194        }
195    }
196
197    /// Manually demote to a narrower PTR type if the trie is small enough.
198    /// Returns `Ok(())` on success, `Err(())` if the trie is too large or
199    /// already at the minimum width (u8).
200    pub fn demote(&mut self) -> Result<(), ()> {
201        // Take ownership of inner via replace, then call consuming demote()
202        let inner = std::mem::replace(&mut self.inner, DynInner::U8(NibbleTrie::new()));
203        match inner {
204            DynInner::U8(t) => {
205                // u8 is the minimum — can't demote further
206                self.inner = DynInner::U8(t);
207                Err(())
208            }
209            DynInner::U16(t) => {
210                match t.demote::<u8>() {
211                    Ok(smaller) => {
212                        self.inner = DynInner::U8(smaller);
213                        Ok(())
214                    }
215                    Err(original) => {
216                        self.inner = DynInner::U16(original);
217                        Err(())
218                    }
219                }
220            }
221            DynInner::U32(t) => {
222                match t.demote::<u16>() {
223                    Ok(smaller) => {
224                        self.inner = DynInner::U16(smaller);
225                        Ok(())
226                    }
227                    Err(original) => {
228                        self.inner = DynInner::U32(original);
229                        Err(())
230                    }
231                }
232            }
233            DynInner::U64(t) => {
234                match t.demote::<u32>() {
235                    Ok(smaller) => {
236                        self.inner = DynInner::U32(smaller);
237                        Ok(())
238                    }
239                    Err(original) => {
240                        self.inner = DynInner::U64(original);
241                        Err(())
242                    }
243                }
244            }
245        }
246    }
247
248    /// Promote the inner trie to the next wider PTR type.
249    fn promote_inner(&mut self) {
250        // We need to take ownership of the inner trie, promote it, and replace.
251        // Use a placeholder that will be immediately overwritten.
252        let inner = std::mem::replace(&mut self.inner, DynInner::U8(NibbleTrie::new()));
253        self.inner = match inner {
254            DynInner::U8(t) => DynInner::U16(t.promote()),
255            DynInner::U16(t) => DynInner::U32(t.promote()),
256            DynInner::U32(t) => DynInner::U64(t.promote()),
257            DynInner::U64(t) => {
258                // u64 is the maximum — this should never happen
259                // (near_capacity returns false for u64 in practice)
260                DynInner::U64(t)
261            }
262        };
263    }
264}
265
266impl<T> Default for DynTrie<T> {
267    fn default() -> Self {
268        Self::new()
269    }
270}
271
272#[cfg(test)]
273#[path = "tests/dyn_trie.rs"]
274mod tests;
275