Skip to main content

jstd/
registry.rs

1//! Strongly typed identifiers and registries.
2//!
3//! This module provides:
4//! - `Identifier`: a trait for strongly typed IDs backed by `usize`
5//! - `Identified<Id, T>`: a value paired with its typed ID
6//! - `Registry<Id, T>`: a typed vector indexed by `Id`
7//!
8//! # Example
9//! ```
10//! use jstd::Identifier;
11//! use jstd::registry::Registry;
12//!
13//! #[derive(Identifier)]
14//! struct NodeId(usize);
15//!
16//! let mut registry = Registry::<NodeId, &str>::default();
17//! let a = registry.push("a");
18//! let b = registry.push("b");
19//!
20//! assert_eq!(usize::from(a), 0);
21//! assert_eq!(usize::from(b), 1);
22//! assert_eq!(registry[a], "a");
23//! assert_eq!(registry[b], "b");
24//! ```
25use std::{
26    fmt::{Debug, Display},
27    hash::Hash,
28    marker::PhantomData,
29    ops::{Deref, DerefMut, Index, IndexMut},
30    slice, vec,
31};
32
33use crate::intern::Intern;
34use serde::{Deserialize, Deserializer, Serialize, Serializer, ser::SerializeSeq};
35
36/// Typed identifier trait used by [`Registry`].
37///
38/// Any `Identifier` is expected to be a light wrapper over `usize`.
39pub trait Identifier: Copy + Hash + From<usize> + Into<usize> + Eq + Ord + std::fmt::Debug {}
40
41impl Identifier for usize {}
42
43/// A value paired with its typed identifier.
44pub struct Identified<Id: Identifier, T> {
45    pub id: Id,
46    pub inner: T,
47}
48
49impl<Id: Identifier, T> Identified<Id, T> {
50    /// Creates a new identified wrapper.
51    pub fn new(id: Id, data: T) -> Self {
52        Self { id, inner: data }
53    }
54}
55
56impl<Id: Identifier, T> Deref for Identified<Id, T> {
57    type Target = T;
58
59    fn deref(&self) -> &Self::Target {
60        &self.inner
61    }
62}
63
64impl<Id: Identifier, T> DerefMut for Identified<Id, T> {
65    fn deref_mut(&mut self) -> &mut Self::Target {
66        &mut self.inner
67    }
68}
69
70impl<'a, Id: Identifier, T> Identified<Id, &'a mut T> {
71    /// Converts `Identified<Id, &mut T>` into `Identified<Id, &T>`.
72    pub fn immutable(self) -> Identified<Id, &'a T> {
73        Identified::new(self.id, &*self.inner)
74    }
75}
76
77impl<Id: Identifier, T: Display> Display for Identified<Id, T> {
78    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
79        self.inner.fmt(f)
80    }
81}
82
83impl<Id: Identifier, T: Debug> Debug for Identified<Id, T> {
84    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
85        self.inner.fmt(f)
86    }
87}
88
89/// A strongly typed vector
90///
91/// # Example
92/// ```
93/// use jstd::Identifier;
94/// use jstd::registry::Registry;
95///
96/// #[derive(Identifier)]
97/// struct ItemId(usize);
98///
99/// let mut reg = Registry::<ItemId, i32>::default();
100/// let id = reg.push(10);
101/// reg[id] += 5;
102///
103/// assert_eq!(reg[id], 15);
104/// assert_eq!(reg.len(), 1);
105/// assert!(!reg.is_empty());
106/// ```
107/// Segmented, **append-only-stable** backing store: element `n` lives in chunk
108/// `k = floor(log2(n + 1))` at offset `n + 1 - 2^k`, so chunk `k` holds exactly
109/// `2^k` elements. Each chunk is allocated once at its full capacity and never
110/// reallocated, so **an element's address is stable for the life of the
111/// registry** even as later `push`es grow the store (growing appends new chunks;
112/// it never moves existing elements). Growing the outer `Vec<Vec<T>>` moves the
113/// chunk *headers*, not their heap buffers. This stability is what lets the
114/// literal/type interners hand out `&T` references that outlive a mint (see
115/// `qcode`'s `RwLock`-wrapped interners). Chunk sizes double, so a small registry
116/// stays cheap (chunks 1, 2, 4, …) and a large one needs few chunks.
117///
118/// The public API is identical to a flat `Vec`-backed registry: ids are dense
119/// `0..len` and index in insertion order.
120pub struct Registry<Id: Identifier, T> {
121    chunks: Vec<Vec<T>>,
122    len: usize,
123    _marker: PhantomData<Id>,
124}
125
126/// `(chunk, offset)` for global index `n`.
127#[inline]
128fn locate(n: usize) -> (usize, usize) {
129    let m = n + 1;
130    let k = (usize::BITS - 1 - m.leading_zeros()) as usize;
131    (k, m - (1 << k))
132}
133
134impl<Id: Identifier, T: Serialize> Serialize for Registry<Id, T> {
135    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
136    where
137        S: Serializer,
138    {
139        // Same wire format as the old flat registry: a single length-prefixed
140        // sequence of elements in id order. The length must be given up front —
141        // a `collect_seq` over the chunk-`Flatten` iterator has no exact length,
142        // which length-prefixed formats (bincode) reject.
143        let mut seq = serializer.serialize_seq(Some(self.len))?;
144        for e in self.chunks.iter().flatten() {
145            seq.serialize_element(e)?;
146        }
147        seq.end()
148    }
149}
150
151impl<'de, Id: Identifier, T: Deserialize<'de>> Deserialize<'de> for Registry<Id, T> {
152    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
153    where
154        D: Deserializer<'de>,
155    {
156        Ok(Vec::<T>::deserialize(deserializer)?.into_iter().collect())
157    }
158}
159
160impl<Id: Identifier, T: Clone> Clone for Registry<Id, T> {
161    fn clone(&self) -> Self {
162        // Rebuild through `push` so each chunk is reallocated at its full
163        // capacity (a derived `Vec` clone would shrink the last chunk to its
164        // length and break the never-realloc stability invariant on next push).
165        self.chunks.iter().flatten().cloned().collect()
166    }
167}
168
169impl<Id: Identifier, T: Debug> Debug for Registry<Id, T> {
170    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
171        f.debug_list()
172            .entries(self.chunks.iter().flatten())
173            .finish()
174    }
175}
176
177impl<Id: Identifier, T: PartialEq> PartialEq for Registry<Id, T> {
178    fn eq(&self, other: &Self) -> bool {
179        self.len == other.len
180            && self
181                .chunks
182                .iter()
183                .flatten()
184                .eq(other.chunks.iter().flatten())
185    }
186}
187
188impl<Id: Identifier, T: Eq> Eq for Registry<Id, T> {}
189
190impl<Id: Identifier, T> Registry<Id, T> {
191    /// Pushes a value and returns its typed identifier.
192    pub fn push(&mut self, e: T) -> Id {
193        let n = self.len;
194        let (k, offset) = locate(n);
195        if offset == 0 {
196            // First element of a fresh chunk `k`; earlier chunks are already full.
197            self.chunks.push(Vec::with_capacity(1 << k));
198        }
199        self.chunks[k].push(e);
200        self.len += 1;
201        n.into()
202    }
203
204    /// Returns the number of elements.
205    pub fn len(&self) -> usize {
206        self.len
207    }
208
209    /// Returns `true` if the registry contains no elements.
210    pub fn is_empty(&self) -> bool {
211        self.len == 0
212    }
213
214    #[inline]
215    #[track_caller]
216    fn at(&self, n: usize) -> &T {
217        debug_assert!(
218            n < self.len,
219            "registry index {n} out of bounds for length {}",
220            self.len
221        );
222        let (k, offset) = locate(n);
223        &self.chunks[k][offset]
224    }
225
226    #[inline]
227    #[track_caller]
228    fn at_mut(&mut self, n: usize) -> &mut T {
229        debug_assert!(
230            n < self.len,
231            "registry index {n} out of bounds for length {}",
232            self.len
233        );
234        let (k, offset) = locate(n);
235        &mut self.chunks[k][offset]
236    }
237
238    /// Replaces the element at `id`, returning the previous value. The id (and
239    /// every element's stable address) is unchanged. Used to *check out* an
240    /// element — swap in a sentinel, own the original, swap it back later —
241    /// without disturbing any other id.
242    ///
243    /// # Panics
244    /// Panics if `id` is out of bounds.
245    #[track_caller]
246    pub fn replace(&mut self, id: Id, value: T) -> T {
247        std::mem::replace(self.at_mut(id.into()), value)
248    }
249
250    /// Returns an immutable identified view of an element.
251    ///
252    /// # Panics
253    /// Panics if `id` is out of bounds.
254    #[track_caller]
255    pub fn get(&self, id: Id) -> Identified<Id, &T> {
256        Identified::new(id, self.at(id.into()))
257    }
258
259    /// Returns a mutable identified view of an element.
260    ///
261    /// # Panics
262    /// Panics if `id` is out of bounds.
263    #[track_caller]
264    pub fn get_mut(&mut self, id: Id) -> Identified<Id, &mut T> {
265        Identified::new(id, self.at_mut(id.into()))
266    }
267
268    /// Iterates immutably over `(id, value)` as [`Identified`] items.
269    ///
270    /// # Example
271    /// ```
272    /// use jstd::Identifier;
273    /// use jstd::registry::Registry;
274    ///
275    /// #[derive(Identifier)]
276    /// struct Id(usize);
277    ///
278    /// let mut reg = Registry::<Id, &str>::default();
279    /// reg.push("x");
280    /// reg.push("y");
281    ///
282    /// let ids: Vec<usize> = reg.iter().map(|item| usize::from(item.id)).collect();
283    /// let vals: Vec<&str> = reg.iter().map(|item| **item).collect();
284    ///
285    /// assert_eq!(ids, vec![0, 1]);
286    /// assert_eq!(vals, vec!["x", "y"]);
287    /// ```
288    pub fn iter(&self) -> Iter<'_, Id, T> {
289        Iter {
290            iter: self.chunks.iter().flatten(),
291            index: 0,
292            _marker: PhantomData,
293        }
294    }
295
296    /// Iterates mutably over `(id, value)` as [`Identified`] items.
297    ///
298    /// # Example
299    /// ```
300    /// use jstd::Identifier;
301    /// use jstd::registry::Registry;
302    ///
303    /// #[derive(Identifier)]
304    /// struct Id(usize);
305    ///
306    /// let mut reg = Registry::<Id, i32>::default();
307    /// reg.push(1);
308    /// reg.push(2);
309    ///
310    /// for mut item in reg.iter_mut() {
311    ///     **item += 10;
312    /// }
313    ///
314    /// assert_eq!(reg[Id::from(0)], 11);
315    /// assert_eq!(reg[Id::from(1)], 12);
316    /// ```
317    pub fn iter_mut(&mut self) -> IterMut<'_, Id, T> {
318        IterMut {
319            iter: self.chunks.iter_mut().flatten(),
320            index: 0,
321            _marker: PhantomData,
322        }
323    }
324
325    /// Borrows the elements at `ids` mutably and disjointly, returned in the same
326    /// order as `ids`.
327    ///
328    /// This is the disjoint-`&mut`-slice primitive the parallel function-pass
329    /// driver uses to hand each worker its own body straight out of the registry,
330    /// without the checkout/checkin swap (context-split stage 5b-ii, see
331    /// `docs/plans/context-split/05b-plan.md` §2.2). Because every returned
332    /// reference comes from a *distinct* [`iter_mut`](Self::iter_mut) slot, the
333    /// borrows are provably non-overlapping and no `unsafe` is required.
334    ///
335    /// `ids` must be **distinct** and in bounds; the returned vector has one
336    /// reference per requested id, positionally aligned with `ids`.
337    ///
338    /// # Panics
339    /// Panics if `ids` contains a duplicate id or an out-of-bounds id.
340    pub fn select_mut(&mut self, ids: &[Id]) -> Vec<&mut T> {
341        // Map each requested global index to its position in `ids`, asserting
342        // distinctness and bounds up front so a caller bug is a loud panic, never
343        // a silently-shortened result.
344        let mut want: std::collections::HashMap<usize, usize> =
345            std::collections::HashMap::with_capacity(ids.len());
346        for (pos, id) in ids.iter().enumerate() {
347            let n: usize = (*id).into();
348            assert!(
349                n < self.len,
350                "select_mut: id index {n} out of bounds (len {})",
351                self.len
352            );
353            let prev = want.insert(n, pos);
354            assert!(prev.is_none(), "select_mut: duplicate id index {n}");
355        }
356
357        // One `iter_mut` pass: each disjoint `&mut T` is routed to its requested
358        // output slot. `iter_mut` yields ids in ascending global order; `want`
359        // restores the caller's order.
360        let mut slots: Vec<Option<&mut T>> = (0..ids.len()).map(|_| None).collect();
361        for item in self.iter_mut() {
362            let n: usize = item.id.into();
363            if let Some(&pos) = want.get(&n) {
364                slots[pos] = Some(item.inner);
365            }
366        }
367        slots
368            .into_iter()
369            .map(|slot| slot.expect("select_mut: requested id had no backing slot"))
370            .collect()
371    }
372}
373
374pub struct Iter<'a, Id: Identifier, T> {
375    iter: std::iter::Flatten<slice::Iter<'a, Vec<T>>>,
376    index: usize,
377    _marker: PhantomData<Id>,
378}
379
380impl<'a, Id: Identifier, T> Iterator for Iter<'a, Id, T> {
381    type Item = Identified<Id, &'a T>;
382
383    fn next(&mut self) -> Option<Self::Item> {
384        let value = self.iter.next()?;
385        let id = Id::from(self.index);
386        self.index += 1;
387
388        Some(Identified::new(id, value))
389    }
390}
391
392pub struct IterMut<'a, Id: Identifier, T> {
393    iter: std::iter::Flatten<slice::IterMut<'a, Vec<T>>>,
394    index: usize,
395    _marker: PhantomData<Id>,
396}
397
398impl<'a, Id: Identifier, T> Iterator for IterMut<'a, Id, T> {
399    type Item = Identified<Id, &'a mut T>;
400
401    fn next(&mut self) -> Option<Self::Item> {
402        let value = self.iter.next()?;
403        let id = Id::from(self.index);
404        self.index += 1;
405
406        Some(Identified::new(id, value))
407    }
408}
409
410pub struct IntoIter<Id: Identifier, T> {
411    iter: std::iter::Flatten<vec::IntoIter<Vec<T>>>,
412    index: usize,
413    _marker: PhantomData<Id>,
414}
415
416impl<Id: Identifier, T> Iterator for IntoIter<Id, T> {
417    type Item = Identified<Id, T>;
418
419    fn next(&mut self) -> Option<Self::Item> {
420        let value = self.iter.next()?;
421        let id = Id::from(self.index);
422        self.index += 1;
423
424        Some(Identified::new(id, value))
425    }
426}
427
428impl<Id: Identifier, T> Default for Registry<Id, T> {
429    fn default() -> Self {
430        Self {
431            chunks: Vec::new(),
432            len: 0,
433            _marker: PhantomData,
434        }
435    }
436}
437
438impl<'a, Id: Identifier, T> IntoIterator for &'a Registry<Id, T> {
439    type Item = Identified<Id, &'a T>;
440
441    type IntoIter = Iter<'a, Id, T>;
442
443    fn into_iter(self) -> Self::IntoIter {
444        self.iter()
445    }
446}
447
448impl<'a, Id: Identifier, T> IntoIterator for &'a mut Registry<Id, T> {
449    type Item = Identified<Id, &'a mut T>;
450
451    type IntoIter = IterMut<'a, Id, T>;
452
453    fn into_iter(self) -> Self::IntoIter {
454        self.iter_mut()
455    }
456}
457
458impl<Id: Identifier, T> IntoIterator for Registry<Id, T> {
459    type Item = Identified<Id, T>;
460
461    type IntoIter = IntoIter<Id, T>;
462
463    fn into_iter(self) -> Self::IntoIter {
464        IntoIter {
465            iter: self.chunks.into_iter().flatten(),
466            index: 0,
467            _marker: PhantomData,
468        }
469    }
470}
471
472impl<Id: Identifier, T> FromIterator<T> for Registry<Id, T> {
473    fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
474        let mut reg = Self::default();
475        for e in iter {
476            reg.push(e);
477        }
478        reg
479    }
480}
481
482impl<Id: Identifier, T> Index<Id> for Registry<Id, T> {
483    type Output = T;
484
485    #[track_caller]
486    fn index(&self, index: Id) -> &Self::Output {
487        self.at(index.into())
488    }
489}
490
491impl<Id: Identifier, T> IndexMut<Id> for Registry<Id, T> {
492    #[track_caller]
493    fn index_mut(&mut self, index: Id) -> &mut Self::Output {
494        self.at_mut(index.into())
495    }
496}
497
498impl<Id: Identifier, T: Intern> Intern for Registry<Id, T> {
499    type Static = Registry<Id, T::Static>;
500
501    fn intern(self, pool: &mut super::intern::StringPool) -> Self::Static {
502        self.into_iter()
503            .map(|item| item.inner.intern(pool))
504            .collect()
505    }
506}
507
508#[cfg(test)]
509mod tests {
510    use super::*;
511    use jstd_derive::Identifier;
512
513    #[derive(Identifier)]
514    struct Id(usize);
515
516    /// Segment math places element `n` in chunk `floor(log2(n+1))`.
517    #[test]
518    fn locate_matches_doubling_layout() {
519        assert_eq!(locate(0), (0, 0)); // chunk 0 (size 1)
520        assert_eq!(locate(1), (1, 0)); // chunk 1 (size 2)
521        assert_eq!(locate(2), (1, 1));
522        assert_eq!(locate(3), (2, 0)); // chunk 2 (size 4)
523        assert_eq!(locate(6), (2, 3));
524        assert_eq!(locate(7), (3, 0)); // chunk 3 (size 8)
525    }
526
527    /// Dense ids, `len`, and index order survive spanning many chunks.
528    #[test]
529    fn push_index_iter_across_chunks() {
530        let mut reg = Registry::<Id, usize>::default();
531        let ids: Vec<Id> = (0..1000).map(|v| reg.push(v)).collect();
532        assert_eq!(reg.len(), 1000);
533        for (i, &id) in ids.iter().enumerate() {
534            assert_eq!(usize::from(id), i);
535            assert_eq!(reg[id], i);
536        }
537        let seen: Vec<usize> = reg.iter().map(|item| *item.inner).collect();
538        assert_eq!(seen, (0..1000).collect::<Vec<_>>());
539    }
540
541    /// The core invariant: an element's address is stable across later pushes
542    /// (later pushes only append new chunks; existing chunks never reallocate).
543    #[test]
544    fn element_address_is_stable_across_pushes() {
545        let mut reg = Registry::<Id, usize>::default();
546        let first = reg.push(42);
547        let addr = &reg[first] as *const usize;
548        for v in 0..10_000 {
549            reg.push(v);
550        }
551        assert_eq!(
552            &reg[first] as *const usize, addr,
553            "address moved after growth"
554        );
555        assert_eq!(reg[first], 42);
556    }
557
558    /// `replace` swaps contents in place, returning the old value and leaving the
559    /// slot's address (and all other ids) untouched.
560    #[test]
561    fn replace_swaps_in_place() {
562        let mut reg = Registry::<Id, i32>::default();
563        let a = reg.push(1);
564        let b = reg.push(2);
565        let addr_b = &reg[b] as *const i32;
566        let old = reg.replace(b, 99);
567        assert_eq!(old, 2);
568        assert_eq!(reg[b], 99);
569        assert_eq!(reg[a], 1, "other ids untouched");
570        assert_eq!(&reg[b] as *const i32, addr_b, "slot address stable");
571        assert_eq!(reg.len(), 2, "len unchanged");
572    }
573
574    #[test]
575    #[cfg(debug_assertions)]
576    #[should_panic(expected = "registry index 0 out of bounds for length 0")]
577    fn indexing_reports_requested_index_and_length() {
578        let reg = Registry::<Id, usize>::default();
579        let _ = reg[Id::from(0)];
580    }
581
582    /// `select_mut` returns disjoint mutable borrows in the caller's id order
583    /// (not ascending id order), spanning multiple chunks, and lets each be
584    /// written independently.
585    #[test]
586    fn select_mut_disjoint_in_input_order() {
587        let mut reg = Registry::<Id, usize>::default();
588        let ids: Vec<Id> = (0..100).map(|v| reg.push(v)).collect();
589
590        // Deliberately out of order and spanning chunk boundaries.
591        let picked = [ids[7], ids[0], ids[63], ids[64], ids[2]];
592        let refs = reg.select_mut(&picked);
593        assert_eq!(refs.len(), picked.len());
594        // Order matches `picked`, not ascending id order.
595        assert_eq!(
596            refs.iter().map(|r| **r).collect::<Vec<_>>(),
597            vec![7, 0, 63, 64, 2]
598        );
599        // Disjoint: mutate every borrow, then observe all writes landed.
600        for r in refs {
601            *r += 1000;
602        }
603        for &id in &picked {
604            assert_eq!(reg[id], usize::from(id) + 1000);
605        }
606        // Untouched ids are unchanged.
607        assert_eq!(reg[ids[1]], 1);
608    }
609
610    /// A duplicate id in the request is a loud panic (would otherwise alias).
611    #[test]
612    #[should_panic(expected = "duplicate id")]
613    fn select_mut_rejects_duplicates() {
614        let mut reg = Registry::<Id, usize>::default();
615        let a = reg.push(1);
616        reg.push(2);
617        let _ = reg.select_mut(&[a, a]);
618    }
619
620    /// Rebuilding from a flat element sequence (the path `Deserialize` and
621    /// `Clone` take through `FromIterator`) reproduces a chunked registry equal to
622    /// the original, spanning several chunks.
623    #[test]
624    fn rebuild_from_flat_sequence() {
625        let reg: Registry<Id, i32> = (0..300).collect();
626        let flat: Vec<i32> = reg.iter().map(|item| *item.inner).collect();
627        let back: Registry<Id, i32> = flat.into_iter().collect();
628        assert_eq!(reg, back);
629        assert_eq!(back.len(), 300);
630    }
631
632    /// Clone preserves contents and the never-realloc stability invariant: a
633    /// clone's last (partial) chunk must still absorb a push without moving its
634    /// existing elements.
635    #[test]
636    fn clone_preserves_stability() {
637        let mut reg = Registry::<Id, usize>::default();
638        for v in 0..5 {
639            reg.push(v); // ends mid-chunk (chunk 2 holds indices 3,4 of capacity 4)
640        }
641        let mut cloned = reg.clone();
642        assert_eq!(reg, cloned);
643        let last = Id::from(4);
644        let addr = &cloned[last] as *const usize;
645        cloned.push(99); // fills the partial chunk without reallocating it
646        assert_eq!(
647            &cloned[last] as *const usize, addr,
648            "clone's chunk reallocated"
649        );
650    }
651
652    #[test]
653    fn public_views_iterators_and_serialization_preserve_values() {
654        let mut reg = Registry::<Id, i32>::default();
655        assert!(reg.is_empty());
656        let first = reg.push(10);
657        let second = reg.push(20);
658        assert_eq!(reg.get(first).id, first);
659        assert_eq!(**reg.get(second), 20);
660
661        let mut mutable = reg.get_mut(first);
662        **mutable += 5;
663        let immutable = mutable.immutable();
664        assert_eq!(immutable.id, first);
665        assert_eq!(**immutable, 15);
666        assert_eq!(format!("{immutable}"), "15");
667        assert_eq!(format!("{immutable:?}"), "15");
668
669        let mut borrowed = (&reg).into_iter();
670        assert_eq!(borrowed.size_hint(), (0, None));
671        assert_eq!(borrowed.next().unwrap().id, first);
672        assert_eq!(borrowed.next().unwrap().id, second);
673        assert!(borrowed.next().is_none());
674
675        let owned: Vec<_> = reg.clone().into_iter().map(|entry| entry.inner).collect();
676        assert_eq!(owned, [15, 20]);
677        assert_eq!(format!("{reg:?}"), "[15, 20]");
678
679        let bytes = bincode::serde::encode_to_vec(&reg, bincode::config::standard()).unwrap();
680        let (decoded, used): (Registry<Id, i32>, _) =
681            bincode::serde::decode_from_slice(&bytes, bincode::config::standard()).unwrap();
682        assert_eq!(used, bytes.len());
683        assert_eq!(decoded, reg);
684    }
685}