Skip to main content

jstd/
stable_arena.rs

1//! Dense payload storage with stable, never-reused logical identifiers.
2//!
3//! [`StableArena`] separates identity from physical position. Removing an entry
4//! eagerly drops its payload with `swap_remove`, while a location table keeps
5//! every surviving identifier valid. Unlike [`Registry`](crate::registry::Registry),
6//! payload addresses are not stable across mutation.
7
8use std::{
9    fmt::{Debug, Formatter},
10    iter::Zip,
11    ops::{Index, IndexMut},
12    slice, vec,
13};
14
15use serde::{Deserialize, Deserializer, Serialize, Serializer};
16
17use crate::registry::{Identified, Identifier};
18
19const DEAD: usize = usize::MAX;
20
21/// Stable logical IDs over compact, dense physical payload storage.
22#[derive(Clone, PartialEq, Eq)]
23pub struct StableArena<Id: Identifier, T> {
24    values: Vec<T>,
25    slot_ids: Vec<Id>,
26    locations: Vec<usize>,
27}
28
29impl<Id: Identifier, T> StableArena<Id, T> {
30    /// Inserts `value` under a fresh, monotonic identifier.
31    pub fn push(&mut self, value: T) -> Id {
32        let raw = self.locations.len();
33        assert!(raw < DEAD, "StableArena identifier space exhausted");
34        let id = Id::from(raw);
35        assert_eq!(
36            Into::<usize>::into(id),
37            raw,
38            "StableArena identifier does not round-trip through its backing type"
39        );
40
41        let slot = self.values.len();
42        assert!(slot < DEAD, "StableArena physical slot space exhausted");
43        self.values.push(value);
44        self.slot_ids.push(id);
45        self.locations.push(slot);
46        id
47    }
48
49    /// Returns the number of live payloads.
50    pub fn len(&self) -> usize {
51        self.values.len()
52    }
53
54    /// Returns the number of logical IDs issued, including removed IDs.
55    pub fn issued_len(&self) -> usize {
56        self.locations.len()
57    }
58
59    /// Returns the payload capacity reserved by the dense value vector.
60    pub fn capacity(&self) -> usize {
61        self.values.capacity()
62    }
63
64    /// Returns bytes reserved by the arena's three structural vectors.
65    ///
66    /// Allocations owned by `T` itself are deliberately excluded.
67    pub fn structural_bytes(&self) -> usize {
68        self.values
69            .capacity()
70            .saturating_mul(std::mem::size_of::<T>())
71            .saturating_add(
72                self.slot_ids
73                    .capacity()
74                    .saturating_mul(std::mem::size_of::<Id>()),
75            )
76            .saturating_add(
77                self.locations
78                    .capacity()
79                    .saturating_mul(std::mem::size_of::<usize>()),
80            )
81    }
82
83    /// Returns `true` when no live payload remains.
84    pub fn is_empty(&self) -> bool {
85        self.values.is_empty()
86    }
87
88    /// Releases structural capacity retained beyond the current live/issued
89    /// lengths.
90    ///
91    /// Identifiers, liveness, and dense physical order are unchanged; only
92    /// allocator capacity is returned. Intended for explicit end-of-mutation
93    /// boundaries — shrinking after every removal would reallocate on the next
94    /// growth.
95    pub fn shrink_to_fit(&mut self) {
96        self.values.shrink_to_fit();
97        self.slot_ids.shrink_to_fit();
98        self.locations.shrink_to_fit();
99    }
100
101    /// Returns whether `id` currently names a live payload.
102    pub fn contains(&self, id: Id) -> bool {
103        self.live_slot(id).is_some()
104    }
105
106    /// Returns the live payload identified by `id`, if any.
107    pub fn get(&self, id: Id) -> Option<Identified<Id, &T>> {
108        let slot = self.live_slot(id)?;
109        Some(Identified::new(id, &self.values[slot]))
110    }
111
112    /// Returns the live payload identified by `id` mutably, if any.
113    pub fn get_mut(&mut self, id: Id) -> Option<Identified<Id, &mut T>> {
114        let slot = self.live_slot(id)?;
115        Some(Identified::new(id, &mut self.values[slot]))
116    }
117
118    /// Removes and returns `id`'s payload while preserving every other ID.
119    ///
120    /// # Panics
121    ///
122    /// Panics when `id` is unknown or was already removed.
123    pub fn remove(&mut self, id: Id) -> T {
124        let raw = id.into();
125        let slot = self.live_slot(id).unwrap_or_else(|| {
126            panic!(
127                "StableArena::remove: dead or unknown id {id:?} (issued {}, live {})",
128                self.issued_len(),
129                self.len()
130            )
131        });
132
133        self.locations[raw] = DEAD;
134        let removed = self.values.swap_remove(slot);
135        let removed_id = self.slot_ids.swap_remove(slot);
136        debug_assert_eq!(removed_id, id);
137
138        if slot < self.values.len() {
139            let moved_id = self.slot_ids[slot];
140            self.locations[Into::<usize>::into(moved_id)] = slot;
141        }
142        removed
143    }
144
145    /// Drops every payload and starts issuing ids from zero again, keeping
146    /// the capacity already allocated.
147    ///
148    /// Every id issued before is invalid afterwards, and *will be issued
149    /// again*: this begins a new epoch of the arena, and a stale id from the
150    /// previous one silently names a different payload. Only an owner that
151    /// can vouch no such id survives may call this.
152    pub fn clear(&mut self) {
153        self.values.clear();
154        self.slot_ids.clear();
155        self.locations.clear();
156    }
157
158    /// Removes every payload whose id is `issued` or above and stops counting
159    /// those ids as issued, so the next push reuses the first of them.
160    ///
161    /// The payloads are removed newest first, so an arena that only ever
162    /// pushed since the ids below `issued` were issued keeps its dense order.
163    /// Like [`clear`](Self::clear), this reissues ids: it is for taking back
164    /// a batch of pushes whose ids never escaped.
165    pub fn truncate_issued(&mut self, issued: usize) {
166        for raw in (issued..self.locations.len()).rev() {
167            let id = Id::from(raw);
168            if self.contains(id) {
169                self.remove(id);
170            }
171        }
172        self.locations.truncate(issued);
173    }
174
175    /// Iterates over live entries in dense physical order.
176    pub fn iter(&self) -> Iter<'_, Id, T> {
177        Iter {
178            inner: self.slot_ids.iter().zip(self.values.iter()),
179        }
180    }
181
182    /// Iterates mutably over live entries in dense physical order.
183    pub fn iter_mut(&mut self) -> IterMut<'_, Id, T> {
184        IterMut {
185            inner: self.slot_ids.iter().zip(self.values.iter_mut()),
186        }
187    }
188
189    #[inline]
190    fn live_slot(&self, id: Id) -> Option<usize> {
191        let slot = *self.locations.get(Into::<usize>::into(id))?;
192        (slot != DEAD).then_some(slot)
193    }
194}
195
196impl<Id: Identifier, T> Default for StableArena<Id, T> {
197    fn default() -> Self {
198        Self {
199            values: Vec::new(),
200            slot_ids: Vec::new(),
201            locations: Vec::new(),
202        }
203    }
204}
205
206impl<Id: Identifier, T: Debug> Debug for StableArena<Id, T> {
207    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
208        f.debug_struct("StableArena")
209            .field("issued", &self.issued_len())
210            .field(
211                "entries",
212                &self
213                    .iter()
214                    .map(|entry| (entry.id, entry.inner))
215                    .collect::<Vec<_>>(),
216            )
217            .finish()
218    }
219}
220
221impl<Id: Identifier, T> Index<Id> for StableArena<Id, T> {
222    type Output = T;
223
224    fn index(&self, id: Id) -> &Self::Output {
225        self.get(id).map(|entry| entry.inner).unwrap_or_else(|| {
226            panic!(
227                "StableArena index: dead or unknown id {id:?} (issued {}, live {})",
228                self.issued_len(),
229                self.len()
230            )
231        })
232    }
233}
234
235impl<Id: Identifier, T> IndexMut<Id> for StableArena<Id, T> {
236    fn index_mut(&mut self, id: Id) -> &mut Self::Output {
237        let issued = self.issued_len();
238        let live = self.len();
239        self.get_mut(id)
240            .map(|entry| entry.inner)
241            .unwrap_or_else(|| {
242                panic!(
243                    "StableArena mutable index: dead or unknown id {id:?} (issued {issued}, live {live})"
244                )
245            })
246    }
247}
248
249/// Immutable dense-order iterator.
250pub struct Iter<'a, Id: Identifier, T> {
251    inner: Zip<slice::Iter<'a, Id>, slice::Iter<'a, T>>,
252}
253
254impl<'a, Id: Identifier, T> Iterator for Iter<'a, Id, T> {
255    type Item = Identified<Id, &'a T>;
256
257    fn next(&mut self) -> Option<Self::Item> {
258        let (id, value) = self.inner.next()?;
259        Some(Identified::new(*id, value))
260    }
261
262    fn size_hint(&self) -> (usize, Option<usize>) {
263        self.inner.size_hint()
264    }
265}
266
267impl<Id: Identifier, T> ExactSizeIterator for Iter<'_, Id, T> {}
268
269/// Mutable dense-order iterator.
270pub struct IterMut<'a, Id: Identifier, T> {
271    inner: Zip<slice::Iter<'a, Id>, slice::IterMut<'a, T>>,
272}
273
274impl<'a, Id: Identifier, T> Iterator for IterMut<'a, Id, T> {
275    type Item = Identified<Id, &'a mut T>;
276
277    fn next(&mut self) -> Option<Self::Item> {
278        let (id, value) = self.inner.next()?;
279        Some(Identified::new(*id, value))
280    }
281
282    fn size_hint(&self) -> (usize, Option<usize>) {
283        self.inner.size_hint()
284    }
285}
286
287impl<Id: Identifier, T> ExactSizeIterator for IterMut<'_, Id, T> {}
288
289/// Consuming dense-order iterator.
290pub struct IntoIter<Id: Identifier, T> {
291    inner: Zip<vec::IntoIter<Id>, vec::IntoIter<T>>,
292}
293
294impl<Id: Identifier, T> Iterator for IntoIter<Id, T> {
295    type Item = Identified<Id, T>;
296
297    fn next(&mut self) -> Option<Self::Item> {
298        let (id, value) = self.inner.next()?;
299        Some(Identified::new(id, value))
300    }
301
302    fn size_hint(&self) -> (usize, Option<usize>) {
303        self.inner.size_hint()
304    }
305}
306
307impl<Id: Identifier, T> ExactSizeIterator for IntoIter<Id, T> {}
308
309impl<'a, Id: Identifier, T> IntoIterator for &'a StableArena<Id, T> {
310    type Item = Identified<Id, &'a T>;
311    type IntoIter = Iter<'a, Id, T>;
312
313    fn into_iter(self) -> Self::IntoIter {
314        self.iter()
315    }
316}
317
318impl<'a, Id: Identifier, T> IntoIterator for &'a mut StableArena<Id, T> {
319    type Item = Identified<Id, &'a mut T>;
320    type IntoIter = IterMut<'a, Id, T>;
321
322    fn into_iter(self) -> Self::IntoIter {
323        self.iter_mut()
324    }
325}
326
327impl<Id: Identifier, T> IntoIterator for StableArena<Id, T> {
328    type Item = Identified<Id, T>;
329    type IntoIter = IntoIter<Id, T>;
330
331    fn into_iter(self) -> Self::IntoIter {
332        IntoIter {
333            inner: self.slot_ids.into_iter().zip(self.values),
334        }
335    }
336}
337
338impl<Id: Identifier, T> FromIterator<T> for StableArena<Id, T> {
339    fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
340        let mut arena = Self::default();
341        for value in iter {
342            arena.push(value);
343        }
344        arena
345    }
346}
347
348#[derive(Serialize)]
349struct StableArenaWireRef<'a, T> {
350    next_id: usize,
351    entries: Vec<(usize, &'a T)>,
352}
353
354#[derive(Serialize, Deserialize)]
355struct StableArenaWire<T> {
356    next_id: usize,
357    entries: Vec<(usize, T)>,
358}
359
360impl<Id: Identifier, T: Serialize> Serialize for StableArena<Id, T> {
361    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
362    where
363        S: Serializer,
364    {
365        StableArenaWireRef {
366            next_id: self.issued_len(),
367            entries: self
368                .slot_ids
369                .iter()
370                .copied()
371                .map(Into::<usize>::into)
372                .zip(self.values.iter())
373                .collect(),
374        }
375        .serialize(serializer)
376    }
377}
378
379impl<'de, Id: Identifier, T: Deserialize<'de>> Deserialize<'de> for StableArena<Id, T> {
380    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
381    where
382        D: Deserializer<'de>,
383    {
384        let StableArenaWire { next_id, entries } = StableArenaWire::deserialize(deserializer)?;
385        if next_id == DEAD {
386            return Err(serde::de::Error::custom(
387                "StableArena next ID exhausts the location sentinel",
388            ));
389        }
390        if let Some(last_issued) = next_id.checked_sub(1)
391            && Into::<usize>::into(Id::from(last_issued)) != last_issued
392        {
393            return Err(serde::de::Error::custom(format!(
394                "StableArena next ID {next_id} exceeds its backing type"
395            )));
396        }
397
398        let mut arena = Self {
399            values: Vec::with_capacity(entries.len()),
400            slot_ids: Vec::with_capacity(entries.len()),
401            locations: vec![DEAD; next_id],
402        };
403        for (raw, value) in entries {
404            if raw >= next_id {
405                return Err(serde::de::Error::custom(format!(
406                    "StableArena live ID {raw} is outside next ID {next_id}"
407                )));
408            }
409            let id = Id::from(raw);
410            if Into::<usize>::into(id) != raw {
411                return Err(serde::de::Error::custom(format!(
412                    "StableArena ID {raw} does not fit its backing type"
413                )));
414            }
415            if arena.locations[raw] != DEAD {
416                return Err(serde::de::Error::custom(format!(
417                    "StableArena contains duplicate live ID {raw}"
418                )));
419            }
420            let slot = arena.values.len();
421            arena.locations[raw] = slot;
422            arena.slot_ids.push(id);
423            arena.values.push(value);
424        }
425        Ok(arena)
426    }
427}
428
429#[cfg(test)]
430mod tests {
431    use super::*;
432    use jstd_derive::Identifier;
433
434    #[derive(Identifier)]
435    struct Id(u32);
436
437    #[test]
438    fn clear_starts_a_new_epoch_with_the_old_capacity() {
439        let mut arena = StableArena::<Id, &str>::default();
440        let a = arena.push("a");
441        arena.push("b");
442        arena.remove(a);
443        let capacity = arena.capacity();
444
445        arena.clear();
446        assert!(arena.is_empty());
447        assert_eq!(arena.issued_len(), 0);
448        assert_eq!(arena.capacity(), capacity);
449        assert_eq!(arena.push("c"), a, "ids are issued again");
450        assert_eq!(arena[a], "c");
451    }
452
453    #[test]
454    fn truncate_issued_takes_back_the_newest_ids() {
455        let mut arena = StableArena::<Id, &str>::default();
456        let a = arena.push("a");
457        let b = arena.push("b");
458        let issued = arena.issued_len();
459        let c = arena.push("c");
460        let d = arena.push("d");
461        arena.remove(c);
462
463        arena.truncate_issued(issued);
464        assert_eq!(arena.issued_len(), issued);
465        assert!(!arena.contains(d));
466        assert_eq!(
467            arena.iter().map(|entry| entry.id).collect::<Vec<_>>(),
468            [a, b],
469            "the surviving entries keep their order"
470        );
471        assert_eq!(arena.push("e"), c, "the taken-back ids are reissued");
472    }
473
474    #[test]
475    fn removal_repairs_moved_slot_and_never_reuses_ids() {
476        let mut arena = StableArena::<Id, &str>::default();
477        let a = arena.push("a");
478        let b = arena.push("b");
479        let c = arena.push("c");
480
481        assert_eq!(arena.remove(b), "b");
482        assert!(!arena.contains(b));
483        assert_eq!(arena.get(b).map(|entry| **entry), None);
484        assert_eq!(arena[c], "c");
485        assert_eq!(arena[a], "a");
486        assert_eq!(
487            arena.iter().map(|entry| entry.id).collect::<Vec<_>>(),
488            [a, c]
489        );
490
491        let d = arena.push("d");
492        assert_eq!(usize::from(d), 3);
493        assert_eq!(arena.issued_len(), 4);
494        assert_eq!(arena.len(), 3);
495    }
496
497    #[test]
498    fn removes_first_last_and_only_entries() {
499        let mut arena = StableArena::<Id, i32>::default();
500        let a = arena.push(1);
501        let b = arena.push(2);
502        let c = arena.push(3);
503        assert_eq!(arena.remove(a), 1);
504        assert_eq!(arena.remove(c), 3);
505        assert_eq!(arena.remove(b), 2);
506        assert!(arena.is_empty());
507        assert_eq!(arena.issued_len(), 3);
508    }
509
510    #[test]
511    #[should_panic(expected = "dead or unknown id")]
512    fn indexing_removed_id_panics() {
513        let mut arena = StableArena::<Id, i32>::default();
514        let id = arena.push(1);
515        arena.remove(id);
516        let _ = arena[id];
517    }
518
519    #[test]
520    #[should_panic(expected = "dead or unknown id")]
521    fn double_remove_panics() {
522        let mut arena = StableArena::<Id, i32>::default();
523        let id = arena.push(1);
524        arena.remove(id);
525        arena.remove(id);
526    }
527
528    #[test]
529    fn mutable_and_consuming_iteration_keep_stable_ids() {
530        let mut arena = StableArena::<Id, i32>::default();
531        let a = arena.push(1);
532        let b = arena.push(2);
533        let c = arena.push(3);
534        arena.remove(b);
535        for mut entry in &mut arena {
536            **entry += 10;
537        }
538        let entries = arena
539            .into_iter()
540            .map(|entry| (entry.id, entry.inner))
541            .collect::<Vec<_>>();
542        assert_eq!(entries, [(a, 11), (c, 13)]);
543    }
544
545    #[test]
546    fn shrink_to_fit_preserves_ids_order_and_liveness() {
547        let mut arena = StableArena::<Id, u64>::default();
548        let ids = (0..1_000).map(|step| arena.push(step)).collect::<Vec<_>>();
549        for id in &ids[..900] {
550            arena.remove(*id);
551        }
552        let before = arena.clone();
553        arena.shrink_to_fit();
554        assert_eq!(arena, before);
555        assert!(arena.capacity() < 1_000);
556        assert_eq!(arena.len(), 100);
557        assert_eq!(arena.issued_len(), 1_000);
558        assert_eq!(arena[ids[950]], 950);
559        let next = arena.push(1_000);
560        assert_eq!(usize::from(next), 1_000);
561    }
562
563    #[test]
564    fn clone_and_equality_preserve_holes_and_physical_order() {
565        let mut arena = StableArena::<Id, i32>::default();
566        arena.push(1);
567        let removed = arena.push(2);
568        arena.push(3);
569        arena.remove(removed);
570        let clone = arena.clone();
571        assert_eq!(clone, arena);
572        assert_eq!(
573            clone.iter().map(|entry| entry.id).collect::<Vec<_>>(),
574            arena.iter().map(|entry| entry.id).collect::<Vec<_>>()
575        );
576    }
577
578    #[test]
579    fn serde_round_trip_preserves_holes_order_and_cursor() {
580        let mut arena = StableArena::<Id, String>::default();
581        let a = arena.push("a".into());
582        let b = arena.push("b".into());
583        let c = arena.push("c".into());
584        arena.remove(a);
585        arena.remove(b);
586        let bytes = bincode::serde::encode_to_vec(&arena, bincode::config::standard()).unwrap();
587        let (mut decoded, used): (StableArena<Id, String>, _) =
588            bincode::serde::decode_from_slice(&bytes, bincode::config::standard()).unwrap();
589        assert_eq!(used, bytes.len());
590        assert_eq!(decoded, arena);
591        assert_eq!(decoded[c], "c");
592        let d = decoded.push("d".into());
593        assert_eq!(usize::from(d), 3);
594
595        decoded.remove(c);
596        decoded.remove(d);
597        let bytes = bincode::serde::encode_to_vec(&decoded, bincode::config::standard()).unwrap();
598        let (empty, _): (StableArena<Id, String>, _) =
599            bincode::serde::decode_from_slice(&bytes, bincode::config::standard()).unwrap();
600        assert!(empty.is_empty());
601        assert_eq!(empty.issued_len(), 4);
602    }
603
604    #[test]
605    fn malformed_wire_is_rejected() {
606        let duplicate = StableArenaWire {
607            next_id: 2,
608            entries: vec![(0, 1_i32), (0, 2)],
609        };
610        let bytes = bincode::serde::encode_to_vec(duplicate, bincode::config::standard()).unwrap();
611        assert!(
612            bincode::serde::decode_from_slice::<StableArena<Id, i32>, _>(
613                &bytes,
614                bincode::config::standard()
615            )
616            .is_err()
617        );
618
619        let outside = StableArenaWire {
620            next_id: 1,
621            entries: vec![(1, 1_i32)],
622        };
623        let bytes = bincode::serde::encode_to_vec(outside, bincode::config::standard()).unwrap();
624        assert!(
625            bincode::serde::decode_from_slice::<StableArena<Id, i32>, _>(
626                &bytes,
627                bincode::config::standard()
628            )
629            .is_err()
630        );
631
632        let too_wide = StableArenaWire {
633            next_id: u32::MAX as usize + 2,
634            entries: Vec::<(usize, i32)>::new(),
635        };
636        let bytes = bincode::serde::encode_to_vec(too_wide, bincode::config::standard()).unwrap();
637        assert!(
638            bincode::serde::decode_from_slice::<StableArena<Id, i32>, _>(
639                &bytes,
640                bincode::config::standard()
641            )
642            .is_err()
643        );
644    }
645
646    #[test]
647    fn deterministic_mixed_operations_match_option_vec_model() {
648        let mut arena = StableArena::<Id, u64>::default();
649        let mut model: Vec<Option<u64>> = Vec::new();
650        let mut state = 0x1234_5678_9abc_def0_u64;
651
652        for step in 0..2_000_u64 {
653            state = state
654                .wrapping_mul(6_364_136_223_846_793_005)
655                .wrapping_add(1);
656            let live = model
657                .iter()
658                .enumerate()
659                .filter_map(|(id, value)| value.as_ref().map(|_| id))
660                .collect::<Vec<_>>();
661            if live.is_empty() || state & 3 != 0 {
662                let id = arena.push(step);
663                assert_eq!(usize::from(id), model.len());
664                model.push(Some(step));
665            } else {
666                let raw = live[state as usize % live.len()];
667                let expected = model[raw].take().unwrap();
668                assert_eq!(arena.remove(Id::from(raw)), expected);
669            }
670
671            for (raw, expected) in model.iter().enumerate() {
672                assert_eq!(
673                    arena.get(Id::from(raw)).map(|entry| **entry),
674                    *expected,
675                    "model mismatch at step {step}, id {raw}"
676                );
677            }
678        }
679    }
680
681    #[test]
682    fn public_views_iteration_and_capacity_accounting_work() {
683        let mut arena = StableArena::<Id, i32>::default();
684        assert!(arena.is_empty());
685        assert_eq!(arena.structural_bytes(), 0);
686
687        let first = arena.push(1);
688        let second = arena.push(2);
689        let capacity_before = arena.capacity();
690        assert!(arena.structural_bytes() >= capacity_before * std::mem::size_of::<i32>());
691        assert_eq!(arena.get(first).unwrap().id, first);
692        **arena.get_mut(second).unwrap() = 20;
693        arena[first] = 10;
694        assert_eq!(arena[first], 10);
695        assert_eq!(arena[second], 20);
696
697        let mut entries = arena.iter();
698        assert_eq!(entries.size_hint(), (2, Some(2)));
699        assert_eq!(entries.next().unwrap().id, first);
700        assert_eq!(entries.next().unwrap().id, second);
701        assert!(entries.next().is_none());
702        assert!(format!("{arena:?}").contains("issued"));
703
704        let rebuilt: StableArena<Id, i32> = [3, 4].into_iter().collect();
705        assert_eq!(
706            rebuilt.iter().map(|entry| *entry.inner).collect::<Vec<_>>(),
707            [3, 4]
708        );
709    }
710}