#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub struct Id {
pub index: u32,
pub generation: u64,
}
struct Slot<T> {
generation: u64,
value: Option<T>,
next_free: Option<usize>,
}
pub struct Slab<T> {
slots: Vec<Slot<T>>,
free: Option<usize>,
len: usize,
}
impl<T> Default for Slab<T> {
fn default() -> Self {
Self::with_capacity(0)
}
}
impl<T> Slab<T> {
pub fn with_capacity(capacity: usize) -> Self {
Self {
slots: Vec::with_capacity(capacity),
free: None,
len: 0,
}
}
pub fn insert_with(&mut self, make: impl FnOnce(Id) -> T) -> Id {
let index = self.free.unwrap_or(self.slots.len());
let generation = self.slots.get(index).map_or(0, |slot| slot.generation);
let id = Id {
index: u32::try_from(index).expect("slab slot index overflow"),
generation,
};
let value = make(id);
if index == self.slots.len() {
self.slots.push(Slot {
generation,
value: Some(value),
next_free: None,
});
} else {
let slot = &mut self.slots[index];
assert!(slot.value.is_none());
self.free = slot.next_free.take();
slot.value = Some(value);
}
self.len += 1;
id
}
pub fn insert(&mut self, value: T) -> Id {
self.insert_with(|_| value)
}
pub fn get(&self, id: Id) -> Option<&T> {
let slot = self.slots.get(id.index as usize)?;
if slot.generation != id.generation {
return None;
}
slot.value.as_ref()
}
pub fn get_mut(&mut self, id: Id) -> Option<&mut T> {
let slot = self.slots.get_mut(id.index as usize)?;
if slot.generation != id.generation {
return None;
}
slot.value.as_mut()
}
pub fn remove(&mut self, id: Id) -> Option<T> {
let slot = self.slots.get_mut(id.index as usize)?;
if slot.generation != id.generation {
return None;
}
let value = slot.value.take()?;
self.len -= 1;
if let Some(generation) = slot.generation.checked_add(1) {
slot.generation = generation;
slot.next_free = self.free;
self.free = Some(id.index as usize);
}
Some(value)
}
pub const fn len(&self) -> usize {
self.len
}
pub const fn slots(&self) -> usize {
self.slots.len()
}
pub fn id_at(&self, index: usize) -> Option<Id> {
let slot = self.slots.get(index)?;
slot.value.as_ref().map(|_| Id {
index: u32::try_from(index).expect("slab slot index overflow"),
generation: slot.generation,
})
}
}
#[cfg(test)]
pub mod tests {
use super::*;
use std::panic::{AssertUnwindSafe, catch_unwind};
pub fn set_generation<T>(slab: &mut Slab<T>, id: Id, generation: u64) -> Id {
assert!(slab.get(id).is_some());
slab.slots[id.index as usize].generation = generation;
Id { generation, ..id }
}
#[test]
fn test_insert_access_and_remove() {
let mut slab = Slab::default();
assert_eq!(slab.len(), 0);
assert_eq!(slab.slots(), 0);
assert_eq!(slab.id_at(0), None);
let first = slab.insert(10);
let second = slab.insert(20);
assert_ne!(first.index, second.index);
assert_eq!(slab.len(), 2);
assert_eq!(slab.slots(), 2);
assert_eq!(slab.get(first), Some(&10));
assert_eq!(slab.id_at(first.index as usize), Some(first));
assert_eq!(slab.id_at(second.index as usize), Some(second));
*slab.get_mut(first).unwrap() = 11;
assert_eq!(slab.get(first), Some(&11));
assert_eq!(slab.get(second), Some(&20));
assert_eq!(slab.remove(first), Some(11));
assert_eq!(slab.len(), 1);
assert_eq!(slab.slots(), 2);
assert_eq!(slab.id_at(first.index as usize), None);
assert_eq!(slab.id_at(second.index as usize), Some(second));
assert_eq!(slab.remove(second), Some(20));
assert_eq!(slab.len(), 0);
assert_eq!(slab.slots(), 2);
}
#[test]
fn test_invalid_ids_leave_entries_unchanged() {
let mut slab = Slab::default();
let removed = slab.insert(10);
let live = slab.insert(20);
assert_eq!(slab.remove(removed), Some(10));
for id in [
removed,
Id {
generation: removed.generation + 1,
..removed
},
Id {
generation: live.generation + 1,
..live
},
Id {
index: slab.slots() as u32,
generation: 0,
},
Id {
index: u32::MAX,
generation: u64::MAX,
},
] {
assert!(slab.get(id).is_none(), "{id:?}");
assert!(slab.get_mut(id).is_none(), "{id:?}");
assert!(slab.remove(id).is_none(), "{id:?}");
}
assert_eq!(slab.len(), 1);
assert_eq!(slab.slots(), 2);
assert_eq!(slab.get(live), Some(&20));
assert_eq!(slab.id_at(removed.index as usize), None);
assert_eq!(slab.id_at(slab.slots()), None);
assert_eq!(slab.id_at(usize::MAX), None);
let reused = slab.insert(30);
let appended = slab.insert(40);
assert_eq!(reused.index, removed.index);
assert_eq!(appended.index, 2);
assert_eq!(slab.get(reused), Some(&30));
assert_eq!(slab.len(), 3);
assert_eq!(slab.slots(), 3);
}
#[test]
fn test_reuse_preserves_live_entries_and_rejects_stale_ids() {
let mut slab = Slab::default();
let first = slab.insert_with(|id| id);
let middle = slab.insert_with(|id| id);
let last = slab.insert_with(|id| id);
assert_eq!(slab.get(first), Some(&first));
assert_eq!(slab.remove(first), Some(first));
assert_eq!(slab.remove(last), Some(last));
let reused_last = slab.insert_with(|id| id);
let reused_first = slab.insert_with(|id| id);
for (old, current) in [(last, reused_last), (first, reused_first)] {
assert_eq!(current.index, old.index);
assert_eq!(current.generation, old.generation + 1);
assert_eq!(slab.get(current), Some(¤t));
assert_eq!(slab.id_at(current.index as usize), Some(current));
assert!(slab.get(old).is_none());
assert!(slab.get_mut(old).is_none());
assert!(slab.remove(old).is_none());
}
assert_eq!(slab.get(middle), Some(&middle));
assert_eq!(slab.len(), 3);
assert_eq!(slab.slots(), 3);
}
#[test]
fn test_exhausted_generation_retires_slot() {
let mut slab = Slab::default();
let id = slab.insert(10);
let penultimate = set_generation(&mut slab, id, u64::MAX - 1);
assert_eq!(slab.remove(penultimate), Some(10));
let exhausted = slab.insert(20);
assert_eq!(exhausted.index, id.index);
assert_eq!(exhausted.generation, u64::MAX);
let other = slab.insert(30);
assert_eq!(slab.remove(other), Some(30));
assert_eq!(slab.remove(exhausted), Some(20));
assert_eq!(slab.len(), 0);
assert_eq!(slab.slots(), 2);
assert_eq!(slab.id_at(exhausted.index as usize), None);
assert!(slab.get(exhausted).is_none());
assert!(slab.get_mut(exhausted).is_none());
assert!(slab.remove(exhausted).is_none());
let reused = slab.insert(40);
let appended = slab.insert(50);
assert_eq!(reused.index, other.index);
assert_eq!(appended.index, 2);
assert_eq!(slab.len(), 2);
assert_eq!(slab.slots(), 3);
}
#[test]
fn test_constructor_panic_leaves_slab_unchanged() {
for reuse in [false, true] {
let mut slab = Slab::default();
let live = slab.insert(10);
if reuse {
let vacant = slab.insert(20);
slab.remove(vacant).unwrap();
}
let slots = slab.slots();
let mut selected = None;
let result = catch_unwind(AssertUnwindSafe(|| {
slab.insert_with(|id| {
selected = Some(id);
panic!("constructor");
});
}));
assert!(result.is_err());
assert_eq!(slab.len(), 1);
assert_eq!(slab.slots(), slots);
assert_eq!(slab.get(live), Some(&10));
let next = slab.insert(30);
assert_eq!(Some(next), selected);
assert_eq!(slab.get(next), Some(&30));
assert_eq!(slab.len(), 2);
assert_eq!(slab.slots(), 2);
}
}
}