stable_gen_map
A single-threaded, generational map that lets you:
- insert with
&selfinstead of&mut self, - keep
&Tstable across internal resizes, - and use generational keys to avoid use-after-free bugs.
It’s designed for patterns like graphs, self-referential structures, and arenas where you want to keep &T references around while still inserting new elements, and you want to be able to defer the removal of elements, such as, at the end of a videogame's frame, or turn.
Great for patterns that rely on shared mutability on a single thread, and removes a lot of borrow checker struggles.
Important: This crate is intentionally single-threaded. The map types are not
Sync, and are meant to be used from a single thread only.
Core types
-
StableGenMap<K, T>
A stable generational map storing a sizedTin aBox. Reusing slots does not need any new allocation. This is generally what you would want. -
StableDerefGenMap<K, Derefable>
A stable generational map where each element is a smart pointer that implementsDerefGenMapPromise. You get stable references toDeref::Target, even if the underlyingVecreallocates.
This is the “advanced” variant forBox<T>,Rc<T>,Arc<T>,&T, or custom smart pointers. -
BoxStableDerefGenMap<K, T>
Type alias forStableDerefGenMap<K, Box<T>>.
This is the most ergonomic “owning” deref-based map: the map ownsTviaBox<T>, you still insert with&self, and you get stable&T/&mut Treferences. Preferred overStableGenMapif your element needs to be boxed anyways.
Keys implement the Key trait; you can use the provided DefaultKey or define your own (e.g. with smaller index / generation types).
What you get
insert(&self, value: T) -> (K, &T)insert_with_key(&self, f: impl FnOnce(K) -> T) -> (K, &T)try_insert_with_key(&self, f: impl FnOnce(K) -> Result<T, E>) -> Result<(K, &T), E>
All of these only need &self, not &mut self.
get(&self, key: K) -> Option<&T>get_mut(&mut self, key: K) -> Option<&mut T>remove(&mut self, key: K) -> Option<T>len(&self) -> usizeclear(&mut self)
Complexity
get/get_mut/removeare O(1).insertis O(1) amortized (O(1) unless a resize happens).
Lifetime / safety model
- You can hold
&Tfrom the map and still callinsert(which only needs&self). removeandclearneed&mut self, so you can’t free elements while there are outstanding borrows – enforced by the borrow-checker.- Generational keys (
Key::Gen) mean stale keys simply returnNoneinstead of aliasing newly inserted elements.
Comparison to other data structures
StableGenMap lives in the same space as generational arenas / slot maps, but it’s aimed at a slightly different pattern: inserting with &self while keeping existing &T references alive, in a single-threaded setting where you still sometimes remove elements at well-defined points (e.g. end of a videogame frame).
Rough comparison:
| Crate | Insert API | Removals | Main focus |
|---|---|---|---|
stable_gen_map::StableGenMap |
fn insert(&self, T) -> (K, &T) |
Yes (But with &mut this time) | Stable &T across growth, single-threaded |
slotmap::SlotMap |
fn insert(&mut self, V) -> K |
Yes (with &mut) | General-purpose generational map |
generational_arena::Arena |
fn insert(&mut self, T) -> Index |
Yes (with &mut) | Simple arena with deletion |
slab::Slab |
fn insert(&mut self, T) -> usize |
Yes (with &mut) | Reusable indices, pre-allocated storage |
sharded_slab::Slab |
fn insert(&self, T) -> Option<usize> |
Yes (with &) | Concurrent slab for multi-threaded use |
When to use stable_gen_map
Use stable_gen_map when:
- You want to insert using only a shared reference (
&self), not&mut self. - You want to use patterns that do not rely heavily on ECS
- You need to hold on to
&Tfrom the map while also inserting new elements into the same map. - You like generational keys.
- You’re in a single-threaded or scoped-thread world.
- You still want to remove elements at specific points in your logic, such as:
- at the end of a videogame frame,
- at the end of a simulation tick,
- during a periodic “cleanup” or GC-like pass.
If you don’t specifically need those properties, you could take a look at slotmap, slab, sharded-slab, or generational-arena
Basic example (using StableGenMap)
This shows the main selling point: insert with &self and indirect references via keys.
use ;
use DefaultKey;
use StableGenMap;