#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[non_exhaustive]
pub struct SlotId {
pub(crate) index: usize,
pub(crate) generation: u32,
}
impl SlotId {
pub fn index(self) -> usize {
self.index
}
pub fn generation(self) -> u32 {
self.generation
}
}
impl std::fmt::Display for SlotId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "slot[{}:gen{}]", self.index, self.generation)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SlotArena<T> {
slots: Vec<Option<T>>,
generations: Vec<u32>,
free_list: Vec<usize>,
len: usize,
}
impl<T> SlotArena<T> {
pub fn new() -> Self {
Self {
slots: Vec::new(),
generations: Vec::new(),
free_list: Vec::new(),
len: 0,
}
}
pub fn with_capacity(capacity: usize) -> Self {
Self {
slots: Vec::with_capacity(capacity),
generations: Vec::with_capacity(capacity),
free_list: Vec::new(),
len: 0,
}
}
#[inline]
pub fn insert(&mut self, value: T) -> SlotId {
let idx = if let Some(idx) = self.free_list.pop() {
self.slots[idx] = Some(value);
idx
} else {
self.slots.push(Some(value));
self.generations.push(0);
self.slots.len() - 1
};
self.len += 1;
SlotId {
index: idx,
generation: self.generations[idx],
}
}
#[inline]
pub fn remove(&mut self, id: SlotId) -> Option<T> {
let idx = id.index;
if *self.generations.get(idx)? != id.generation {
return None;
}
let value = self.slots[idx].take()?;
self.generations[idx] = self.generations[idx].wrapping_add(1);
self.free_list.push(idx);
self.len -= 1;
Some(value)
}
#[inline]
pub fn get(&self, id: SlotId) -> Option<&T> {
let idx = id.index;
if *self.generations.get(idx)? != id.generation {
return None;
}
self.slots[idx].as_ref()
}
#[inline]
pub fn get_mut(&mut self, id: SlotId) -> Option<&mut T> {
let idx = id.index;
if *self.generations.get(idx)? != id.generation {
return None;
}
self.slots[idx].as_mut()
}
#[inline]
pub fn contains(&self, id: SlotId) -> bool {
let idx = id.index;
self.generations
.get(idx)
.is_some_and(|&g| g == id.generation && self.slots[idx].is_some())
}
pub fn contains_index(&self, index: usize) -> bool {
self.slots
.get(index)
.map(|slot| slot.is_some())
.unwrap_or(false)
}
pub fn len(&self) -> usize {
self.len
}
pub fn is_empty(&self) -> bool {
self.len == 0
}
pub fn capacity(&self) -> usize {
self.slots.capacity()
}
pub fn reserve(&mut self, additional: usize) {
self.slots.reserve(additional);
self.generations.reserve(additional);
}
pub fn shrink_to_fit(&mut self) {
self.slots.shrink_to_fit();
self.generations.shrink_to_fit();
self.free_list.shrink_to_fit();
}
pub fn clear_shrink(&mut self) {
self.clear();
self.shrink_to_fit();
}
pub fn clear(&mut self) {
self.slots.clear();
self.generations.clear();
self.free_list.clear();
self.len = 0;
}
pub fn iter(&self) -> Iter<'_, T> {
Iter {
inner: self.slots.iter().enumerate(),
generations: &self.generations,
}
}
pub fn iter_mut(&mut self) -> IterMut<'_, T> {
IterMut {
inner: self.slots.iter_mut().enumerate(),
generations: &self.generations,
}
}
pub fn iter_ids(&self) -> IterIds<'_, T> {
IterIds { inner: self.iter() }
}
#[cfg(any(test, debug_assertions))]
pub fn debug_snapshot(&self) -> SlotArenaSnapshot {
let mut free_list = self.free_list.clone();
free_list.sort_unstable();
let mut live_ids: Vec<_> = self.iter_ids().collect();
live_ids.sort_by_key(|id| id.index());
SlotArenaSnapshot {
len: self.len,
slots_len: self.slots.len(),
free_list,
live_ids,
}
}
pub fn approx_bytes(&self) -> usize {
std::mem::size_of::<Self>()
+ self.slots.capacity() * std::mem::size_of::<Option<T>>()
+ self.generations.capacity() * std::mem::size_of::<u32>()
+ self.free_list.capacity() * std::mem::size_of::<usize>()
}
#[cfg(any(test, debug_assertions))]
pub fn debug_validate_invariants(&self) {
assert_eq!(self.slots.len(), self.generations.len());
let live_count = self.slots.iter().filter(|slot| slot.is_some()).count();
assert_eq!(self.len, live_count);
assert!(self.len <= self.slots.len());
let mut seen_free = std::collections::HashSet::new();
for &idx in &self.free_list {
assert!(idx < self.slots.len());
assert!(self.slots[idx].is_none());
assert!(seen_free.insert(idx));
}
assert_eq!(self.slots.len(), self.free_list.len() + self.len);
}
}
#[cfg(any(test, debug_assertions))]
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct SlotArenaSnapshot {
pub len: usize,
pub slots_len: usize,
pub free_list: Vec<usize>,
pub live_ids: Vec<SlotId>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[non_exhaustive]
#[cfg(feature = "concurrency")]
pub struct ShardedSlotId {
shard: usize,
slot: SlotId,
}
#[cfg(feature = "concurrency")]
impl ShardedSlotId {
pub fn shard(self) -> usize {
self.shard
}
pub fn slot(self) -> SlotId {
self.slot
}
}
#[cfg(feature = "concurrency")]
impl std::fmt::Display for ShardedSlotId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"slot[shard{}:{}:gen{}]",
self.shard, self.slot.index, self.slot.generation
)
}
}
#[cfg(feature = "concurrency")]
#[derive(Debug)]
pub struct ShardedSlotArena<T> {
shards: Vec<parking_lot::RwLock<SlotArena<T>>>,
selector: crate::ds::ShardSelector,
next_shard: std::sync::atomic::AtomicUsize,
}
#[cfg(feature = "concurrency")]
impl<T> ShardedSlotArena<T> {
pub fn new(shards: usize) -> Self {
let shards = shards.max(1);
let mut arenas = Vec::with_capacity(shards);
for _ in 0..shards {
arenas.push(parking_lot::RwLock::new(SlotArena::new()));
}
Self {
shards: arenas,
selector: crate::ds::ShardSelector::new(shards, 0),
next_shard: std::sync::atomic::AtomicUsize::new(0),
}
}
pub fn with_capacity(shards: usize, capacity: usize) -> Self {
let shards = shards.max(1);
let mut arenas = Vec::with_capacity(shards);
for _ in 0..shards {
arenas.push(parking_lot::RwLock::new(SlotArena::with_capacity(capacity)));
}
Self {
shards: arenas,
selector: crate::ds::ShardSelector::new(shards, 0),
next_shard: std::sync::atomic::AtomicUsize::new(0),
}
}
#[deprecated(since = "0.6.0", note = "use `with_capacity` instead")]
pub fn with_shards(shards: usize, capacity_per_shard: usize) -> Self {
Self::with_capacity(shards, capacity_per_shard)
}
pub fn with_shards_seed(shards: usize, capacity_per_shard: usize, seed: u64) -> Self {
let shards = shards.max(1);
let mut arenas = Vec::with_capacity(shards);
for _ in 0..shards {
arenas.push(parking_lot::RwLock::new(SlotArena::with_capacity(
capacity_per_shard,
)));
}
Self {
shards: arenas,
selector: crate::ds::ShardSelector::new(shards, seed),
next_shard: std::sync::atomic::AtomicUsize::new(0),
}
}
pub fn shard_for_key<K: std::hash::Hash>(&self, key: &K) -> usize {
self.selector.shard_for_key(key)
}
pub fn shard_count(&self) -> usize {
self.shards.len()
}
pub fn insert(&self, value: T) -> ShardedSlotId {
let shard = self
.next_shard
.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
% self.shards.len();
let mut arena = self.shards[shard].write();
let slot = arena.insert(value);
ShardedSlotId { shard, slot }
}
pub fn remove(&self, id: ShardedSlotId) -> Option<T> {
let mut arena = self.shards.get(id.shard)?.write();
arena.remove(id.slot)
}
pub fn get_with<R>(&self, id: ShardedSlotId, f: impl FnOnce(&T) -> R) -> Option<R> {
let arena = self.shards.get(id.shard)?.read();
arena.get(id.slot).map(f)
}
pub fn get_mut_with<R>(&self, id: ShardedSlotId, f: impl FnOnce(&mut T) -> R) -> Option<R> {
let mut arena = self.shards.get(id.shard)?.write();
arena.get_mut(id.slot).map(f)
}
pub fn contains(&self, id: ShardedSlotId) -> bool {
self.shards
.get(id.shard)
.map(|arena| arena.read().contains(id.slot))
.unwrap_or(false)
}
pub fn len(&self) -> usize {
self.shards.iter().map(|arena| arena.read().len()).sum()
}
pub fn is_empty(&self) -> bool {
self.shards.iter().all(|s| s.read().is_empty())
}
pub fn reserve(&self, additional: usize) {
for arena in &self.shards {
arena.write().reserve(additional);
}
}
pub fn shrink_to_fit(&self) {
for arena in &self.shards {
arena.write().shrink_to_fit();
}
}
pub fn clear_shrink(&self) {
for arena in &self.shards {
arena.write().clear_shrink();
}
}
pub fn approx_bytes(&self) -> usize {
self.shards
.iter()
.map(|arena| arena.read().approx_bytes())
.sum()
}
}
impl<T> Default for SlotArena<T> {
fn default() -> Self {
Self::new()
}
}
impl<T> FromIterator<T> for SlotArena<T> {
fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
let iter = iter.into_iter();
let (lower, _) = iter.size_hint();
let mut arena = SlotArena::with_capacity(lower);
for value in iter {
arena.insert(value);
}
arena
}
}
impl<T> Extend<T> for SlotArena<T> {
fn extend<I: IntoIterator<Item = T>>(&mut self, iter: I) {
let iter = iter.into_iter();
let (lower, _) = iter.size_hint();
self.reserve(lower);
for value in iter {
self.insert(value);
}
}
}
impl<T> std::ops::Index<SlotId> for SlotArena<T> {
type Output = T;
fn index(&self, id: SlotId) -> &T {
self.get(id).expect("invalid or stale SlotId")
}
}
impl<T> std::ops::IndexMut<SlotId> for SlotArena<T> {
fn index_mut(&mut self, id: SlotId) -> &mut T {
self.get_mut(id).expect("invalid or stale SlotId")
}
}
#[derive(Debug, Clone)]
pub struct Iter<'a, T> {
inner: std::iter::Enumerate<std::slice::Iter<'a, Option<T>>>,
generations: &'a [u32],
}
impl<'a, T> Iterator for Iter<'a, T> {
type Item = (SlotId, &'a T);
fn next(&mut self) -> Option<Self::Item> {
loop {
match self.inner.next() {
Some((idx, Some(value))) => {
let id = SlotId {
index: idx,
generation: self.generations[idx],
};
return Some((id, value));
},
Some((_, None)) => continue,
None => return None,
}
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
(0, self.inner.size_hint().1)
}
}
#[derive(Debug)]
pub struct IterMut<'a, T> {
inner: std::iter::Enumerate<std::slice::IterMut<'a, Option<T>>>,
generations: &'a [u32],
}
impl<'a, T> Iterator for IterMut<'a, T> {
type Item = (SlotId, &'a mut T);
fn next(&mut self) -> Option<Self::Item> {
loop {
match self.inner.next() {
Some((idx, Some(value))) => {
let id = SlotId {
index: idx,
generation: self.generations[idx],
};
return Some((id, value));
},
Some((_, None)) => continue,
None => return None,
}
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
(0, self.inner.size_hint().1)
}
}
#[derive(Debug)]
pub struct IntoIter<T> {
inner: std::iter::Enumerate<std::vec::IntoIter<Option<T>>>,
generations: Vec<u32>,
}
impl<T> Iterator for IntoIter<T> {
type Item = (SlotId, T);
fn next(&mut self) -> Option<Self::Item> {
loop {
match self.inner.next() {
Some((idx, Some(value))) => {
let id = SlotId {
index: idx,
generation: self.generations[idx],
};
return Some((id, value));
},
Some((_, None)) => continue,
None => return None,
}
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
(0, self.inner.size_hint().1)
}
}
#[derive(Debug, Clone)]
pub struct IterIds<'a, T> {
inner: Iter<'a, T>,
}
impl<'a, T> Iterator for IterIds<'a, T> {
type Item = SlotId;
fn next(&mut self) -> Option<Self::Item> {
self.inner.next().map(|(id, _)| id)
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.inner.size_hint()
}
}
impl<T> IntoIterator for SlotArena<T> {
type Item = (SlotId, T);
type IntoIter = IntoIter<T>;
fn into_iter(self) -> Self::IntoIter {
IntoIter {
inner: self.slots.into_iter().enumerate(),
generations: self.generations,
}
}
}
impl<'a, T> IntoIterator for &'a SlotArena<T> {
type Item = (SlotId, &'a T);
type IntoIter = Iter<'a, T>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
impl<'a, T> IntoIterator for &'a mut SlotArena<T> {
type Item = (SlotId, &'a mut T);
type IntoIter = IterMut<'a, T>;
fn into_iter(self) -> Self::IntoIter {
self.iter_mut()
}
}
impl<T> std::iter::FusedIterator for Iter<'_, T> {}
impl<T> std::iter::FusedIterator for IterMut<'_, T> {}
impl<T> std::iter::FusedIterator for IntoIter<T> {}
impl<T> std::iter::FusedIterator for IterIds<'_, T> {}
#[cfg(feature = "concurrency")]
#[derive(Debug)]
pub struct ConcurrentSlotArena<T> {
inner: parking_lot::RwLock<SlotArena<T>>,
}
#[cfg(feature = "concurrency")]
impl<T> ConcurrentSlotArena<T> {
pub fn new() -> Self {
Self {
inner: parking_lot::RwLock::new(SlotArena::new()),
}
}
pub fn with_capacity(capacity: usize) -> Self {
Self {
inner: parking_lot::RwLock::new(SlotArena::with_capacity(capacity)),
}
}
pub fn insert(&self, value: T) -> SlotId {
let mut arena = self.inner.write();
arena.insert(value)
}
pub fn remove(&self, id: SlotId) -> Option<T> {
let mut arena = self.inner.write();
arena.remove(id)
}
pub fn get_with<R>(&self, id: SlotId, f: impl FnOnce(&T) -> R) -> Option<R> {
let arena = self.inner.read();
arena.get(id).map(f)
}
pub fn try_get_with<R>(&self, id: SlotId, f: impl FnOnce(&T) -> R) -> Option<R> {
let arena = self.inner.try_read()?;
arena.get(id).map(f)
}
pub fn get_mut_with<R>(&self, id: SlotId, f: impl FnOnce(&mut T) -> R) -> Option<R> {
let mut arena = self.inner.write();
arena.get_mut(id).map(f)
}
pub fn try_get_mut_with<R>(&self, id: SlotId, f: impl FnOnce(&mut T) -> R) -> Option<R> {
let mut arena = self.inner.try_write()?;
arena.get_mut(id).map(f)
}
pub fn contains(&self, id: SlotId) -> bool {
let arena = self.inner.read();
arena.contains(id)
}
pub fn len(&self) -> usize {
let arena = self.inner.read();
arena.len()
}
pub fn is_empty(&self) -> bool {
let arena = self.inner.read();
arena.is_empty()
}
pub fn capacity(&self) -> usize {
let arena = self.inner.read();
arena.capacity()
}
pub fn reserve(&self, additional: usize) {
let mut arena = self.inner.write();
arena.reserve(additional);
}
pub fn shrink_to_fit(&self) {
let mut arena = self.inner.write();
arena.shrink_to_fit();
}
pub fn clear_shrink(&self) {
let mut arena = self.inner.write();
arena.clear_shrink();
}
pub fn clear(&self) {
let mut arena = self.inner.write();
arena.clear();
}
pub fn approx_bytes(&self) -> usize {
let arena = self.inner.read();
arena.approx_bytes()
}
}
#[cfg(feature = "concurrency")]
impl<T> Default for ConcurrentSlotArena<T> {
fn default() -> Self {
Self::new()
}
}
#[cfg(feature = "concurrency")]
impl<T> ConcurrentSlotArena<T> {
pub fn into_inner(self) -> SlotArena<T> {
self.inner.into_inner()
}
}
#[cfg(feature = "concurrency")]
impl<T> ShardedSlotArena<T> {
pub fn into_inner(self) -> Vec<SlotArena<T>> {
self.shards
.into_iter()
.map(|lock| lock.into_inner())
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn slot_arena_insert_remove_reuse() {
let mut arena = SlotArena::new();
let id1 = arena.insert("a");
let id2 = arena.insert("b");
assert_eq!(arena.len(), 2);
assert_eq!(arena.get(id1), Some(&"a"));
assert_eq!(arena.get(id2), Some(&"b"));
assert_eq!(arena.remove(id1), Some("a"));
assert_eq!(arena.len(), 1);
let id3 = arena.insert("c");
assert_eq!(arena.len(), 2);
assert_eq!(arena.get(id3), Some(&"c"));
assert_eq!(id1.index(), id3.index());
}
#[cfg(feature = "concurrency")]
#[test]
fn concurrent_slot_arena_basic_ops() {
let arena = ConcurrentSlotArena::new();
let id = arena.insert(10);
assert_eq!(arena.get_with(id, |v| *v), Some(10));
assert!(arena.contains(id));
assert_eq!(arena.len(), 1);
arena.get_mut_with(id, |v| *v = 20);
assert_eq!(arena.get_with(id, |v| *v), Some(20));
assert_eq!(arena.remove(id), Some(20));
assert!(!arena.contains(id));
assert!(arena.is_empty());
}
#[test]
fn slot_arena_remove_invalid_id_is_none() {
let mut arena: SlotArena<i32> = SlotArena::new();
let id = SlotId {
index: 0,
generation: 0,
};
assert_eq!(arena.remove(id), None);
assert!(!arena.contains(id));
assert!(arena.is_empty());
}
#[test]
fn slot_arena_clear_resets_state() {
let mut arena = SlotArena::with_capacity(4);
let a = arena.insert("a");
let b = arena.insert("b");
assert_eq!(arena.len(), 2);
assert!(arena.contains(a));
assert!(arena.contains(b));
arena.clear();
assert_eq!(arena.len(), 0);
assert!(arena.is_empty());
assert!(!arena.contains(a));
assert!(!arena.contains(b));
assert_eq!(arena.iter().count(), 0);
}
#[test]
fn slot_arena_iter_skips_empty_slots() {
let mut arena = SlotArena::new();
let a = arena.insert("a");
let b = arena.insert("b");
let c = arena.insert("c");
assert_eq!(arena.remove(b), Some("b"));
let mut values: Vec<_> = arena.iter().map(|(_, v)| *v).collect();
values.sort();
assert_eq!(values, vec!["a", "c"]);
assert!(arena.contains(a));
assert!(arena.contains(c));
}
#[test]
fn slot_arena_get_mut_updates_value() {
let mut arena = SlotArena::new();
let id = arena.insert(1);
if let Some(value) = arena.get_mut(id) {
*value = 2;
}
assert_eq!(arena.get(id), Some(&2));
}
#[test]
fn slot_arena_capacity_tracking() {
let arena: SlotArena<i32> = SlotArena::with_capacity(16);
assert!(arena.capacity() >= 16);
assert_eq!(arena.len(), 0);
}
#[test]
fn slot_arena_contains_index_and_iter_ids() {
let mut arena = SlotArena::new();
let a = arena.insert("a");
let b = arena.insert("b");
assert!(arena.contains_index(a.index()));
assert!(arena.contains_index(b.index()));
arena.remove(a);
assert!(!arena.contains_index(a.index()));
let ids: Vec<_> = arena.iter_ids().collect();
assert_eq!(ids, vec![b]);
}
#[test]
fn slot_arena_reserve_grows_capacity() {
let mut arena: SlotArena<i32> = SlotArena::new();
let before = arena.capacity();
arena.reserve(32);
assert!(arena.capacity() >= before + 32);
}
#[test]
fn slot_arena_debug_snapshot() {
let mut arena = SlotArena::new();
let a = arena.insert("a");
let b = arena.insert("b");
arena.remove(a);
let snapshot = arena.debug_snapshot();
assert_eq!(snapshot.len, 1);
assert!(snapshot.live_ids.contains(&b));
assert!(snapshot.free_list.contains(&a.index()));
}
#[cfg(feature = "concurrency")]
#[test]
fn sharded_slot_arena_basic_ops() {
let arena = ShardedSlotArena::new(2);
let a = arena.insert(1);
let b = arena.insert(2);
assert!(arena.contains(a));
assert!(arena.contains(b));
assert_eq!(arena.get_with(a, |v| *v), Some(1));
assert_eq!(arena.remove(b), Some(2));
assert!(!arena.contains(b));
assert_eq!(arena.len(), 1);
}
#[cfg(feature = "concurrency")]
#[test]
fn sharded_slot_arena_shard_for_key() {
let arena: ShardedSlotArena<i32> = ShardedSlotArena::new(4);
let shard = arena.shard_for_key(&"alpha");
assert!(shard < arena.shard_count());
}
#[cfg(feature = "concurrency")]
#[test]
fn sharded_slot_arena_with_seed() {
let arena: ShardedSlotArena<i32> = ShardedSlotArena::with_shards_seed(4, 0, 99);
let shard = arena.shard_for_key(&"alpha");
assert!(shard < arena.shard_count());
}
#[cfg(feature = "concurrency")]
#[test]
fn concurrent_slot_arena_try_ops() {
let arena = ConcurrentSlotArena::new();
let id = arena.insert(1);
assert_eq!(arena.try_get_with(id, |v| *v), Some(1));
arena.try_get_mut_with(id, |v| *v = 2);
assert_eq!(arena.get_with(id, |v| *v), Some(2));
}
#[cfg(feature = "concurrency")]
#[test]
fn concurrent_slot_arena_clear_and_accessors() {
let arena = ConcurrentSlotArena::new();
let a = arena.insert(1);
let b = arena.insert(2);
assert_eq!(arena.get_with(a, |v| *v), Some(1));
assert_eq!(arena.get_with(b, |v| *v), Some(2));
arena.clear();
assert!(arena.is_empty());
assert!(!arena.contains(a));
assert!(!arena.contains(b));
assert_eq!(arena.get_with(a, |v| *v), None);
}
#[cfg(feature = "concurrency")]
#[test]
fn concurrent_slot_arena_get_mut_with_updates_value() {
let arena = ConcurrentSlotArena::new();
let id = arena.insert(5);
arena.get_mut_with(id, |v| *v = 10);
assert_eq!(arena.get_with(id, |v| *v), Some(10));
}
#[test]
fn stale_id_get_returns_none_after_reuse() {
let mut arena = SlotArena::new();
let id_a = arena.insert("alice");
arena.remove(id_a);
let _id_b = arena.insert("bob");
assert_eq!(arena.get(id_a), None);
assert!(!arena.contains(id_a));
}
#[test]
fn stale_id_get_mut_returns_none_after_reuse() {
let mut arena = SlotArena::new();
let id_a = arena.insert(1);
arena.remove(id_a);
let id_b = arena.insert(2);
assert_eq!(arena.get_mut(id_a), None);
assert_eq!(arena.get(id_b), Some(&2));
}
#[test]
fn stale_id_remove_returns_none_after_reuse() {
let mut arena = SlotArena::new();
let id_a = arena.insert(42);
arena.remove(id_a);
let id_b = arena.insert(99);
assert_eq!(arena.remove(id_a), None);
assert_eq!(arena.get(id_b), Some(&99));
assert_eq!(arena.len(), 1);
}
#[test]
fn stale_id_after_multiple_reuse_cycles() {
let mut arena = SlotArena::new();
let id_gen0 = arena.insert("gen0");
arena.remove(id_gen0);
let id_gen1 = arena.insert("gen1");
arena.remove(id_gen1);
let id_gen2 = arena.insert("gen2");
assert_eq!(id_gen0.index(), id_gen1.index());
assert_eq!(id_gen1.index(), id_gen2.index());
assert_eq!(arena.get(id_gen0), None);
assert_eq!(arena.get(id_gen1), None);
assert_eq!(arena.get(id_gen2), Some(&"gen2"));
}
#[test]
fn stale_id_contains_returns_false_after_reuse() {
let mut arena = SlotArena::new();
let stale = arena.insert(1);
arena.remove(stale);
let _new = arena.insert(2);
assert!(!arena.contains(stale));
}
#[test]
fn slot_arena_debug_invariants_hold() {
let mut arena = SlotArena::new();
let a = arena.insert(1);
let b = arena.insert(2);
arena.remove(a);
let _ = arena.insert(3);
assert!(arena.contains(b));
arena.debug_validate_invariants();
}
#[test]
fn iter_yields_all_live_entries() {
let mut arena = SlotArena::new();
let a = arena.insert(10);
let b = arena.insert(20);
let c = arena.insert(30);
let mut pairs: Vec<_> = arena.iter().collect();
pairs.sort_by_key(|(id, _)| *id);
assert_eq!(pairs, vec![(a, &10), (b, &20), (c, &30)]);
}
#[test]
fn iter_skips_removed_slots() {
let mut arena = SlotArena::new();
let a = arena.insert(10);
let b = arena.insert(20);
let c = arena.insert(30);
arena.remove(b);
let mut pairs: Vec<_> = arena.iter().collect();
pairs.sort_by_key(|(id, _)| *id);
assert_eq!(pairs, vec![(a, &10), (c, &30)]);
}
#[test]
fn iter_on_empty_arena() {
let arena = SlotArena::<i32>::new();
assert_eq!(arena.iter().count(), 0);
}
#[test]
fn iter_mut_modifies_values() {
let mut arena = SlotArena::new();
let a = arena.insert(1);
let b = arena.insert(2);
for (_, v) in arena.iter_mut() {
*v += 100;
}
assert_eq!(arena.get(a), Some(&101));
assert_eq!(arena.get(b), Some(&102));
}
#[test]
fn iter_mut_skips_removed_slots() {
let mut arena = SlotArena::new();
let a = arena.insert(1);
let b = arena.insert(2);
let c = arena.insert(3);
arena.remove(b);
let ids: Vec<_> = arena.iter_mut().map(|(id, _)| id).collect();
assert_eq!(ids.len(), 2);
assert!(ids.contains(&a));
assert!(ids.contains(&c));
}
#[test]
fn iter_ids_matches_iter_keys() {
let mut arena = SlotArena::new();
let a = arena.insert("x");
let b = arena.insert("y");
arena.remove(a);
let c = arena.insert("z");
let ids_from_iter: Vec<_> = arena.iter().map(|(id, _)| id).collect();
let ids_from_iter_ids: Vec<_> = arena.iter_ids().collect();
assert_eq!(ids_from_iter, ids_from_iter_ids);
assert!(ids_from_iter_ids.contains(&b));
assert!(ids_from_iter_ids.contains(&c));
}
#[test]
fn into_iter_consumes_arena() {
let mut arena = SlotArena::new();
arena.insert("a");
arena.insert("b");
arena.insert("c");
let pairs: Vec<_> = arena.into_iter().collect();
assert_eq!(pairs.len(), 3);
}
#[test]
fn into_iter_skips_removed_slots() {
let mut arena = SlotArena::new();
let a = arena.insert(10);
arena.insert(20);
arena.insert(30);
arena.remove(a);
let pairs: Vec<_> = arena.into_iter().collect();
assert_eq!(pairs.len(), 2);
}
#[test]
fn into_iter_empty() {
let arena = SlotArena::<i32>::new();
assert_eq!(arena.into_iter().count(), 0);
}
#[test]
fn into_iter_preserves_slot_ids() {
let mut arena = SlotArena::new();
let a = arena.insert("first");
let b = arena.insert("second");
let mut pairs: Vec<_> = arena.into_iter().collect();
pairs.sort_by_key(|(id, _)| *id);
assert_eq!(pairs, vec![(a, "first"), (b, "second")]);
}
#[test]
fn for_loop_ref_borrow() {
let mut arena = SlotArena::new();
arena.insert(1);
arena.insert(2);
let mut sum = 0;
for (_, v) in &arena {
sum += v;
}
assert_eq!(sum, 3);
assert_eq!(arena.len(), 2); }
#[test]
fn for_loop_mut_borrow() {
let mut arena = SlotArena::new();
let a = arena.insert(1);
let b = arena.insert(2);
for (_, v) in &mut arena {
*v += 10;
}
assert_eq!(arena.get(a), Some(&11));
assert_eq!(arena.get(b), Some(&12));
}
#[test]
fn for_loop_owned() {
let mut arena = SlotArena::new();
arena.insert(10);
arena.insert(20);
let mut sum = 0;
for (_, v) in arena {
sum += v;
}
assert_eq!(sum, 30);
}
#[test]
fn iter_count_matches_len() {
let mut arena = SlotArena::new();
let ids: Vec<_> = (0..10).map(|i| arena.insert(i)).collect();
arena.remove(ids[3]);
arena.remove(ids[7]);
assert_eq!(arena.iter().count(), arena.len());
assert_eq!(arena.iter_mut().count(), arena.len());
assert_eq!(arena.iter_ids().count(), arena.len());
}
#[test]
fn iter_after_clear() {
let mut arena = SlotArena::new();
arena.insert("a");
arena.insert("b");
arena.clear();
assert_eq!(arena.iter().count(), 0);
assert_eq!(arena.iter_mut().count(), 0);
assert_eq!(arena.iter_ids().count(), 0);
}
#[test]
fn clone_produces_independent_copy() {
let mut arena = SlotArena::new();
let a = arena.insert(1);
let b = arena.insert(2);
let mut cloned = arena.clone();
assert_eq!(cloned.get(a), Some(&1));
assert_eq!(cloned.get(b), Some(&2));
assert_eq!(cloned.len(), 2);
cloned.remove(a);
assert_eq!(arena.get(a), Some(&1));
assert!(!cloned.contains(a));
}
#[test]
fn clone_empty_arena() {
let arena = SlotArena::<i32>::new();
let cloned = arena.clone();
assert!(cloned.is_empty());
}
#[test]
fn slot_id_ordering() {
let mut arena = SlotArena::new();
let a = arena.insert("a");
let b = arena.insert("b");
let c = arena.insert("c");
assert!(a < b);
assert!(b < c);
let mut ids = [c, a, b];
ids.sort();
assert_eq!(ids, [a, b, c]);
}
#[cfg(feature = "concurrency")]
#[test]
fn sharded_slot_id_ordering() {
let arena = ShardedSlotArena::new(4);
let a = arena.insert(1);
let b = arena.insert(2);
let c = arena.insert(3);
let mut ids = [c, a, b];
ids.sort();
assert_eq!(ids.len(), 3);
}
#[cfg(feature = "concurrency")]
#[test]
fn concurrent_into_inner_allows_iteration() {
let arena = ConcurrentSlotArena::new();
let a = arena.insert(10);
let b = arena.insert(20);
let inner = arena.into_inner();
assert_eq!(inner.get(a), Some(&10));
assert_eq!(inner.get(b), Some(&20));
let pairs: Vec<_> = inner.iter().collect();
assert_eq!(pairs.len(), 2);
}
#[cfg(feature = "concurrency")]
#[test]
fn concurrent_into_inner_into_iter() {
let arena = ConcurrentSlotArena::new();
arena.insert("x");
arena.insert("y");
let pairs: Vec<_> = arena.into_inner().into_iter().collect();
assert_eq!(pairs.len(), 2);
}
#[cfg(feature = "concurrency")]
#[test]
fn sharded_into_inner_returns_all_shards() {
let arena = ShardedSlotArena::new(4);
for i in 0..8 {
arena.insert(i);
}
let shards = arena.into_inner();
assert_eq!(shards.len(), 4);
let total: usize = shards.iter().map(|s| s.len()).sum();
assert_eq!(total, 8);
}
#[cfg(feature = "concurrency")]
#[test]
fn sharded_into_inner_allows_iteration() {
let arena = ShardedSlotArena::new(2);
arena.insert(1);
arena.insert(2);
arena.insert(3);
let shards = arena.into_inner();
let all_values: Vec<_> = shards
.into_iter()
.flat_map(|s| s.into_iter().map(|(_, v)| v))
.collect();
assert_eq!(all_values.len(), 3);
}
}
#[cfg(test)]
mod property_tests {
use super::*;
use proptest::prelude::*;
proptest! {
#[cfg_attr(miri, ignore)]
#[test]
fn prop_insert_returns_valid_id(
values in prop::collection::vec(any::<u32>(), 0..50)
) {
let mut arena = SlotArena::new();
for value in values {
let id = arena.insert(value);
prop_assert_eq!(arena.get(id), Some(&value));
prop_assert!(arena.contains(id));
}
}
#[cfg_attr(miri, ignore)]
#[test]
fn prop_insert_and_get(
value in any::<u32>()
) {
let mut arena = SlotArena::new();
let id = arena.insert(value);
prop_assert_eq!(arena.get(id), Some(&value));
}
}
proptest! {
#[cfg_attr(miri, ignore)]
#[test]
fn prop_remove_decreases_length(
values in prop::collection::vec(any::<u32>(), 1..30)
) {
let mut arena = SlotArena::new();
let mut ids = Vec::new();
for value in &values {
let id = arena.insert(*value);
ids.push(id);
}
for id in ids {
let old_len = arena.len();
let removed = arena.remove(id);
prop_assert!(removed.is_some());
prop_assert_eq!(arena.len(), old_len - 1);
prop_assert!(!arena.contains(id));
}
prop_assert!(arena.is_empty());
}
#[cfg_attr(miri, ignore)]
#[test]
fn prop_remove_returns_value(
values in prop::collection::vec(any::<u32>(), 1..30)
) {
let mut arena = SlotArena::new();
let mut ids = Vec::new();
for value in &values {
let id = arena.insert(*value);
ids.push((id, *value));
}
for (id, expected_value) in ids {
let removed = arena.remove(id);
prop_assert_eq!(removed, Some(expected_value));
}
}
#[cfg_attr(miri, ignore)]
#[test]
fn prop_remove_twice_returns_none(
value in any::<u32>()
) {
let mut arena = SlotArena::new();
let id = arena.insert(value);
let first = arena.remove(id);
let second = arena.remove(id);
prop_assert_eq!(first, Some(value));
prop_assert_eq!(second, None);
}
}
proptest! {
#[cfg_attr(miri, ignore)]
#[test]
fn prop_slot_id_stable_until_removed(
values in prop::collection::vec(any::<u32>(), 1..30)
) {
let mut arena = SlotArena::new();
let mut ids = Vec::new();
for value in &values {
let id = arena.insert(*value);
ids.push((id, *value));
}
for (id, value) in &ids {
prop_assert_eq!(arena.get(*id), Some(value));
prop_assert!(arena.contains(*id));
}
for (idx, (id, _value)) in ids.iter().enumerate() {
if idx % 2 == 0 {
arena.remove(*id);
}
}
for (idx, (id, value)) in ids.iter().enumerate() {
if idx % 2 != 0 {
prop_assert_eq!(arena.get(*id), Some(value));
} else {
prop_assert_eq!(arena.get(*id), None);
}
}
}
}
proptest! {
#[cfg_attr(miri, ignore)]
#[test]
fn prop_free_slot_reuse(
values1 in prop::collection::vec(any::<u32>(), 5..20),
values2 in prop::collection::vec(any::<u32>(), 5..20)
) {
let mut arena = SlotArena::new();
let mut ids = Vec::new();
for value in &values1 {
let id = arena.insert(*value);
ids.push(id);
}
let removed_indices: Vec<_> = ids.iter().map(|id| id.index()).collect();
for id in ids {
arena.remove(id);
}
prop_assert!(arena.is_empty());
let mut reused_count = 0;
for value in &values2 {
let id = arena.insert(*value);
if removed_indices.contains(&id.index()) {
reused_count += 1;
}
}
prop_assert!(reused_count > 0);
}
}
proptest! {
#[cfg_attr(miri, ignore)]
#[test]
fn prop_len_tracks_entries(
values in prop::collection::vec(any::<u32>(), 0..50)
) {
let mut arena = SlotArena::new();
for (idx, value) in values.iter().enumerate() {
arena.insert(*value);
prop_assert_eq!(arena.len(), idx + 1);
}
let total = values.len();
let mut ids: Vec<_> = arena.iter_ids().collect();
for (idx, id) in ids.drain(..).enumerate() {
arena.remove(id);
prop_assert_eq!(arena.len(), total - idx - 1);
}
prop_assert_eq!(arena.len(), 0);
}
#[cfg_attr(miri, ignore)]
#[test]
fn prop_is_empty_consistent(
values in prop::collection::vec(any::<u32>(), 0..30)
) {
let mut arena = SlotArena::new();
for value in values {
arena.insert(value);
if arena.is_empty() {
prop_assert_eq!(arena.len(), 0);
} else {
prop_assert!(!arena.is_empty());
}
}
}
}
proptest! {
#[cfg_attr(miri, ignore)]
#[test]
fn prop_get_returns_inserted_values(
values in prop::collection::vec(any::<u32>(), 1..30)
) {
let mut arena = SlotArena::new();
let mut ids = Vec::new();
for value in &values {
let id = arena.insert(*value);
ids.push((id, *value));
}
for (id, value) in ids {
prop_assert_eq!(arena.get(id), Some(&value));
}
}
#[cfg_attr(miri, ignore)]
#[test]
fn prop_contains_consistent_with_get(
values in prop::collection::vec(any::<u32>(), 1..30)
) {
let mut arena = SlotArena::new();
let mut ids = Vec::new();
for value in &values {
let id = arena.insert(*value);
ids.push(id);
}
for id in ids {
let contains = arena.contains(id);
let get_result = arena.get(id);
if contains {
prop_assert!(get_result.is_some());
} else {
prop_assert!(get_result.is_none());
}
}
}
}
proptest! {
#[cfg_attr(miri, ignore)]
#[test]
fn prop_get_mut_modifies(
value1 in any::<u32>(),
value2 in any::<u32>()
) {
let mut arena = SlotArena::new();
let id = arena.insert(value1);
if let Some(val) = arena.get_mut(id) {
*val = value2;
}
prop_assert_eq!(arena.get(id), Some(&value2));
}
}
proptest! {
#[cfg_attr(miri, ignore)]
#[test]
fn prop_iter_returns_len_items(
values in prop::collection::vec(any::<u32>(), 0..50)
) {
let mut arena = SlotArena::new();
for value in values {
arena.insert(value);
}
let iter_count = arena.iter().count();
prop_assert_eq!(iter_count, arena.len());
}
#[cfg_attr(miri, ignore)]
#[test]
fn prop_iter_ids_returns_len_items(
values in prop::collection::vec(any::<u32>(), 0..50)
) {
let mut arena = SlotArena::new();
for value in values {
arena.insert(value);
}
let ids_count = arena.iter_ids().count();
prop_assert_eq!(ids_count, arena.len());
}
#[cfg_attr(miri, ignore)]
#[test]
fn prop_iter_skips_removed(
values in prop::collection::vec(any::<u32>(), 5..30)
) {
let mut arena = SlotArena::new();
let mut ids = Vec::new();
for value in &values {
let id = arena.insert(*value);
ids.push(id);
}
for (idx, id) in ids.iter().enumerate() {
if idx % 2 == 0 {
arena.remove(*id);
}
}
let iter_count = arena.iter().count();
let expected_count = values.len() / 2;
prop_assert_eq!(iter_count, expected_count);
}
}
proptest! {
#[cfg_attr(miri, ignore)]
#[test]
fn prop_clear_resets_state(
values in prop::collection::vec(any::<u32>(), 1..30)
) {
let mut arena = SlotArena::new();
for value in values {
arena.insert(value);
}
arena.clear_shrink();
prop_assert!(arena.is_empty());
prop_assert_eq!(arena.len(), 0);
prop_assert_eq!(arena.iter().count(), 0);
}
#[cfg_attr(miri, ignore)]
#[test]
fn prop_clear_invalidates_ids(
values in prop::collection::vec(any::<u32>(), 1..20)
) {
let mut arena = SlotArena::new();
let mut ids = Vec::new();
for value in values {
let id = arena.insert(value);
ids.push(id);
}
arena.clear_shrink();
for id in ids {
prop_assert!(!arena.contains(id));
prop_assert_eq!(arena.get(id), None);
}
}
#[cfg_attr(miri, ignore)]
#[test]
fn prop_usable_after_clear(
values1 in prop::collection::vec(any::<u32>(), 1..20),
values2 in prop::collection::vec(any::<u32>(), 1..20)
) {
let mut arena = SlotArena::new();
for value in values1 {
arena.insert(value);
}
arena.clear_shrink();
for value in &values2 {
arena.insert(*value);
}
prop_assert_eq!(arena.len(), values2.len());
}
}
proptest! {
#[cfg_attr(miri, ignore)]
#[test]
fn prop_with_capacity_preallocates(
capacity in 1usize..100
) {
let arena: SlotArena<u32> = SlotArena::with_capacity(capacity);
prop_assert!(arena.capacity() >= capacity);
prop_assert_eq!(arena.len(), 0);
prop_assert!(arena.is_empty());
}
}
proptest! {
#[cfg_attr(miri, ignore)]
#[test]
fn prop_matches_hashmap(
operations in prop::collection::vec((0u8..3, any::<u32>()), 0..50)
) {
let mut arena = SlotArena::new();
let mut reference: std::collections::HashMap<SlotId, u32> = std::collections::HashMap::new();
for (op, value) in operations {
match op % 3 {
0 => {
let id = arena.insert(value);
reference.insert(id, value);
}
1 => {
if !reference.is_empty() {
let keys: Vec<_> = reference.keys().copied().collect();
let id = keys[0];
let arena_val = arena.remove(id);
let ref_val = reference.remove(&id);
prop_assert_eq!(arena_val, ref_val);
}
}
2 => {
for (&id, &expected_value) in &reference {
prop_assert_eq!(arena.get(id), Some(&expected_value));
}
}
_ => unreachable!(),
}
prop_assert_eq!(arena.len(), reference.len());
prop_assert_eq!(arena.is_empty(), reference.is_empty());
}
}
}
}