cachekit/ds/slot_arena.rs
1//! Slot arena with stable [`SlotId`] handles.
2//!
3//! Provides arena-style allocation where elements are stored in a `Vec<Option<T>>`
4//! and freed slots are tracked in a free list for reuse. Handles ([`SlotId`]) remain
5//! stable across insertions and deletions, making this ideal for graph structures,
6//! linked lists, and policy metadata.
7//!
8//! ## Architecture
9//!
10//! ```text
11//! ┌────────────────────────────────────────────────────────────────────────────┐
12//! │ SlotArena Layout │
13//! │ │
14//! │ ┌───────────────────────────────────────────────────────────────────┐ │
15//! │ │ slots: Vec<Option<T>> │ │
16//! │ │ │ │
17//! │ │ index: 0 1 2 3 4 5 │ │
18//! │ │ ┌────┐ ┌────┐ ┌────┐ ┌────┐ ┌────┐ ┌────┐ │ │
19//! │ │ │ T │ │None│ │ T │ │None│ │ T │ │None│ │ │
20//! │ │ │"a" │ │ │ │"c" │ │ │ │"e" │ │ │ │ │
21//! │ │ └────┘ └────┘ └────┘ └────┘ └────┘ └────┘ │ │
22//! │ │ ▲ │ ▲ │ ▲ │ │ │
23//! │ │ │ │ │ │ │ │ │ │
24//! │ │ SlotId(0) │ SlotId(2) │ SlotId(4) │ │ │
25//! │ │ │ │ │ │ │
26//! │ └──────────────────────┼───────────────┼───────────────┼────────────┘ │
27//! │ │ │ │ │
28//! │ ┌──────────────────────┼───────────────┼───────────────┼────────────┐ │
29//! │ │ free_list: Vec<usize> │ │ │ │
30//! │ │ │ │ │ │ │
31//! │ │ ┌───────┴───────┬───────┴───────┬───────┴───────┐ │ │
32//! │ │ │ 1 │ 3 │ 5 │ │ │
33//! │ │ └───────────────┴───────────────┴───────────────┘ │ │
34//! │ │ │ │
35//! │ │ ◄─────── pop() returns 5 for next insert ──────► │ │
36//! │ └───────────────────────────────────────────────────────────────────┘ │
37//! │ │
38//! │ len: 3 (number of live entries, not slots.len()) │
39//! └────────────────────────────────────────────────────────────────────────────┘
40//!
41//! Slot Lifecycle
42//! ──────────────
43//!
44//! insert("x"):
45//! ├─► free_list.pop() ──► Some(idx) ──► slots[idx] = Some("x")
46//! │ return SlotId(idx)
47//! │
48//! └─► None ──► slots.push(Some("x"))
49//! return SlotId(slots.len() - 1)
50//!
51//! remove(SlotId(2)):
52//! slots[2].take() ──► Some("c") ──► free_list.push(2)
53//! return Some("c")
54//!
55//! get(SlotId(2)):
56//! slots.get(2) ──► Some(&slot) ──► slot.as_ref() ──► Option<&T>
57//!
58//! Arena Variants
59//! ──────────────
60//!
61//! ┌─────────────────────────────────────────────────────────────────────┐
62//! │ SlotArena<T> Single-threaded, direct &T access │
63//! ├─────────────────────────────────────────────────────────────────────┤
64//! │ ConcurrentSlotArena<T> Thread-safe via RwLock │
65//! │ Uses closures: get_with(id, |v| ...) │
66//! ├─────────────────────────────────────────────────────────────────────┤
67//! │ ShardedSlotArena<T> Multiple RwLock-protected arenas │
68//! │ Round-robin insert, ShardedSlotId handle │
69//! └─────────────────────────────────────────────────────────────────────┘
70//! ```
71//!
72//! ## Key Components
73//!
74//! - [`SlotId`]: Stable handle wrapping a `usize` index
75//! - [`SlotArena`]: Single-threaded arena with O(1) operations
76//! - [`ConcurrentSlotArena`]: Thread-safe wrapper with closure-based access
77//! - [`ShardedSlotArena`]: Sharded arena for high-concurrency workloads
78//! - [`ShardedSlotId`]: Handle containing shard index + slot id
79//!
80//! ## Operations
81//!
82//! | Operation | Description | Complexity |
83//! |---------------|------------------------------------------|------------|
84//! | `insert` | Add value, reuse free slot if available | O(1) |
85//! | `remove` | Remove and return value, free the slot | O(1) |
86//! | `get` | Get reference by SlotId | O(1) |
87//! | `get_mut` | Get mutable reference by SlotId | O(1) |
88//! | `contains` | Check if SlotId is valid | O(1) |
89//! | `iter` | Iterate over live entries | O(n) |
90//!
91//! ## Performance Characteristics
92//!
93//! - **No per-operation allocation**: Slots are reused, Vec grows as needed
94//! - **Cache-friendly**: Elements stored contiguously in memory
95//! - **Stable handles**: SlotId values remain valid until removed
96//! - **O(1) operations**: All single-element operations are constant time
97//!
98//! ## When to Use
99//!
100//! - Intrusive data structures (linked lists, trees, graphs)
101//! - Policy metadata storage (LRU list nodes, frequency counters)
102//! - Any scenario requiring stable handles that survive mutations
103//! - Memory pools where allocation churn is a concern
104//!
105//! ## Example Usage
106//!
107//! ```
108//! use cachekit::ds::SlotArena;
109//!
110//! let mut arena = SlotArena::new();
111//!
112//! // Insert values and get stable handles
113//! let id_a = arena.insert("alice");
114//! let id_b = arena.insert("bob");
115//!
116//! // Access by handle
117//! assert_eq!(arena.get(id_a), Some(&"alice"));
118//!
119//! // Remove frees the slot
120//! assert_eq!(arena.remove(id_b), Some("bob"));
121//!
122//! // Next insert reuses freed slot
123//! let id_c = arena.insert("carol");
124//! assert_eq!(id_b.index(), id_c.index()); // Same underlying index
125//! ```
126//!
127//! ## Thread Safety
128//!
129//! - [`SlotArena`]: Not thread-safe, use in single-threaded contexts
130//! - [`ConcurrentSlotArena`]: Thread-safe via `parking_lot::RwLock`
131//! - [`ShardedSlotArena`]: Thread-safe with per-shard locks
132//!
133//! ## Implementation Notes
134//!
135//! - `SlotId` indices may be reused after removal
136//! - Accessing a stale `SlotId` returns `None` (not undefined behavior)
137//! - `len()` tracks live entries, not `slots.len()`
138//! - `debug_validate_invariants()` available in debug/test builds
139/// Stable handle into a [`SlotArena`].
140///
141/// A lightweight identifier (wrapping a `usize` index) that provides O(1)
142/// access to arena slots. Handles remain valid until the slot is removed;
143/// after removal, the index may be reused by a later `insert`.
144///
145/// # Stability
146///
147/// Unlike raw indices or pointers, `SlotId` values are semantically tied to
148/// the slot they reference. Accessing a removed slot returns `None` rather
149/// than causing undefined behavior.
150///
151/// # Example
152///
153/// ```
154/// use cachekit::ds::SlotArena;
155///
156/// let mut arena = SlotArena::new();
157/// let id = arena.insert(42);
158///
159/// // SlotId provides O(1) access
160/// assert_eq!(arena.get(id), Some(&42));
161///
162/// // Inspect raw index for debugging
163/// println!("Value stored at index {}", id.index());
164///
165/// // After removal, same index may be reused
166/// arena.remove(id);
167/// let new_id = arena.insert(100);
168/// assert_eq!(id.index(), new_id.index());
169/// ```
170#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
171pub struct SlotId(pub(crate) usize);
172
173impl SlotId {
174 /// Returns the underlying slot index.
175 ///
176 /// Useful for debugging, logging, or custom data structures that need
177 /// to work with raw indices.
178 pub fn index(self) -> usize {
179 self.0
180 }
181}
182
183/// Single-threaded arena with stable [`SlotId`] handles and O(1) operations.
184///
185/// Stores values in a `Vec<Option<T>>` and reuses freed slots via a free list.
186/// Ideal for graph structures, linked lists, and policy metadata where stable
187/// handles are needed.
188///
189/// # Example
190///
191/// ```
192/// use cachekit::ds::SlotArena;
193///
194/// let mut arena = SlotArena::new();
195///
196/// // Insert returns stable handles
197/// let a = arena.insert("first");
198/// let b = arena.insert("second");
199/// let c = arena.insert("third");
200///
201/// assert_eq!(arena.len(), 3);
202/// assert_eq!(arena.get(b), Some(&"second"));
203///
204/// // Remove frees the slot
205/// arena.remove(b);
206/// assert_eq!(arena.len(), 2);
207/// assert_eq!(arena.get(b), None);
208///
209/// // Next insert reuses the freed slot
210/// let d = arena.insert("fourth");
211/// assert_eq!(b.index(), d.index());
212///
213/// // Iterate over live entries
214/// for (id, value) in arena.iter() {
215/// println!("{}: {}", id.index(), value);
216/// }
217/// ```
218///
219/// # Use Case: Intrusive Linked List
220///
221/// ```
222/// use cachekit::ds::{SlotArena, SlotId};
223///
224/// struct Node {
225/// value: i32,
226/// next: Option<SlotId>,
227/// }
228///
229/// let mut arena = SlotArena::new();
230///
231/// // Build a linked list: 1 -> 2 -> 3
232/// let c = arena.insert(Node { value: 3, next: None });
233/// let b = arena.insert(Node { value: 2, next: Some(c) });
234/// let a = arena.insert(Node { value: 1, next: Some(b) });
235///
236/// // Traverse
237/// let mut current = Some(a);
238/// while let Some(id) = current {
239/// let node = arena.get(id).unwrap();
240/// println!("{}", node.value);
241/// current = node.next;
242/// }
243/// ```
244#[derive(Debug)]
245pub struct SlotArena<T> {
246 slots: Vec<Option<T>>,
247 free_list: Vec<usize>,
248 len: usize,
249}
250
251impl<T> SlotArena<T> {
252 /// Creates an empty arena.
253 ///
254 /// # Example
255 ///
256 /// ```
257 /// use cachekit::ds::SlotArena;
258 ///
259 /// let arena: SlotArena<String> = SlotArena::new();
260 /// assert!(arena.is_empty());
261 /// ```
262 pub fn new() -> Self {
263 Self {
264 slots: Vec::new(),
265 free_list: Vec::new(),
266 len: 0,
267 }
268 }
269
270 /// Creates an empty arena with pre-allocated capacity.
271 ///
272 /// # Example
273 ///
274 /// ```
275 /// use cachekit::ds::SlotArena;
276 ///
277 /// let arena: SlotArena<i32> = SlotArena::with_capacity(100);
278 /// assert!(arena.capacity() >= 100);
279 /// assert!(arena.is_empty());
280 /// ```
281 pub fn with_capacity(capacity: usize) -> Self {
282 Self {
283 slots: Vec::with_capacity(capacity),
284 free_list: Vec::new(),
285 len: 0,
286 }
287 }
288
289 /// Inserts a value and returns its [`SlotId`].
290 ///
291 /// Reuses a freed slot if available, otherwise grows the internal Vec.
292 ///
293 /// # Example
294 ///
295 /// ```
296 /// use cachekit::ds::SlotArena;
297 ///
298 /// let mut arena = SlotArena::new();
299 /// let id = arena.insert("hello");
300 /// assert_eq!(arena.get(id), Some(&"hello"));
301 /// ```
302 #[inline]
303 pub fn insert(&mut self, value: T) -> SlotId {
304 let idx = if let Some(idx) = self.free_list.pop() {
305 self.slots[idx] = Some(value);
306 idx
307 } else {
308 self.slots.push(Some(value));
309 self.slots.len() - 1
310 };
311 self.len += 1;
312 SlotId(idx)
313 }
314
315 /// Removes and returns the value at `id`, freeing the slot for reuse.
316 ///
317 /// Returns `None` if the slot is empty or out of bounds.
318 ///
319 /// # Example
320 ///
321 /// ```
322 /// use cachekit::ds::SlotArena;
323 ///
324 /// let mut arena = SlotArena::new();
325 /// let id = arena.insert(42);
326 ///
327 /// assert_eq!(arena.remove(id), Some(42));
328 /// assert_eq!(arena.remove(id), None); // Already removed
329 /// ```
330 #[inline]
331 pub fn remove(&mut self, id: SlotId) -> Option<T> {
332 let slot = self.slots.get_mut(id.0)?;
333 let value = slot.take()?;
334 self.free_list.push(id.0);
335 self.len -= 1;
336 Some(value)
337 }
338
339 /// Returns a shared reference to the value at `id`.
340 ///
341 /// # Example
342 ///
343 /// ```
344 /// use cachekit::ds::SlotArena;
345 ///
346 /// let mut arena = SlotArena::new();
347 /// let id = arena.insert("value");
348 ///
349 /// assert_eq!(arena.get(id), Some(&"value"));
350 /// ```
351 #[inline]
352 pub fn get(&self, id: SlotId) -> Option<&T> {
353 self.slots.get(id.0).and_then(|slot| slot.as_ref())
354 }
355
356 /// Returns a mutable reference to the value at `id`.
357 ///
358 /// # Example
359 ///
360 /// ```
361 /// use cachekit::ds::SlotArena;
362 ///
363 /// let mut arena = SlotArena::new();
364 /// let id = arena.insert(1);
365 ///
366 /// if let Some(v) = arena.get_mut(id) {
367 /// *v = 2;
368 /// }
369 /// assert_eq!(arena.get(id), Some(&2));
370 /// ```
371 #[inline]
372 pub fn get_mut(&mut self, id: SlotId) -> Option<&mut T> {
373 self.slots.get_mut(id.0).and_then(|slot| slot.as_mut())
374 }
375
376 /// Returns `true` if `id` refers to a live slot.
377 ///
378 /// # Example
379 ///
380 /// ```
381 /// use cachekit::ds::SlotArena;
382 ///
383 /// let mut arena = SlotArena::new();
384 /// let id = arena.insert(1);
385 ///
386 /// assert!(arena.contains(id));
387 /// arena.remove(id);
388 /// assert!(!arena.contains(id));
389 /// ```
390 #[inline]
391 pub fn contains(&self, id: SlotId) -> bool {
392 self.slots
393 .get(id.0)
394 .map(|slot| slot.is_some())
395 .unwrap_or(false)
396 }
397
398 /// Returns `true` if the raw `index` is in bounds and occupied.
399 ///
400 /// # Example
401 ///
402 /// ```
403 /// use cachekit::ds::SlotArena;
404 ///
405 /// let mut arena = SlotArena::new();
406 /// let id = arena.insert("value");
407 ///
408 /// assert!(arena.contains_index(id.index()));
409 /// assert!(!arena.contains_index(999));
410 /// ```
411 pub fn contains_index(&self, index: usize) -> bool {
412 self.slots
413 .get(index)
414 .map(|slot| slot.is_some())
415 .unwrap_or(false)
416 }
417
418 /// Returns the number of live entries.
419 ///
420 /// # Example
421 ///
422 /// ```
423 /// use cachekit::ds::SlotArena;
424 ///
425 /// let mut arena = SlotArena::new();
426 /// assert_eq!(arena.len(), 0);
427 ///
428 /// arena.insert("a");
429 /// arena.insert("b");
430 /// assert_eq!(arena.len(), 2);
431 /// ```
432 pub fn len(&self) -> usize {
433 self.len
434 }
435
436 /// Returns `true` if the arena has no live entries.
437 ///
438 /// # Example
439 ///
440 /// ```
441 /// use cachekit::ds::SlotArena;
442 ///
443 /// let mut arena = SlotArena::new();
444 /// assert!(arena.is_empty());
445 ///
446 /// let id = arena.insert(1);
447 /// assert!(!arena.is_empty());
448 ///
449 /// arena.remove(id);
450 /// assert!(arena.is_empty());
451 /// ```
452 pub fn is_empty(&self) -> bool {
453 self.len == 0
454 }
455
456 /// Returns the backing Vec capacity (slots that fit without reallocation).
457 ///
458 /// # Example
459 ///
460 /// ```
461 /// use cachekit::ds::SlotArena;
462 ///
463 /// let arena: SlotArena<i32> = SlotArena::with_capacity(100);
464 /// assert!(arena.capacity() >= 100);
465 /// ```
466 pub fn capacity(&self) -> usize {
467 self.slots.capacity()
468 }
469
470 /// Reserves capacity for at least `additional` more slots.
471 ///
472 /// # Example
473 ///
474 /// ```
475 /// use cachekit::ds::SlotArena;
476 ///
477 /// let mut arena: SlotArena<i32> = SlotArena::new();
478 /// arena.reserve_slots(100);
479 /// assert!(arena.capacity() >= 100);
480 /// ```
481 pub fn reserve_slots(&mut self, additional: usize) {
482 self.slots.reserve(additional);
483 }
484
485 /// Shrinks internal storage to fit the current state.
486 ///
487 /// # Example
488 ///
489 /// ```
490 /// use cachekit::ds::SlotArena;
491 ///
492 /// let mut arena: SlotArena<i32> = SlotArena::with_capacity(1000);
493 /// arena.insert(1);
494 /// arena.shrink_to_fit();
495 /// // Capacity reduced (exact value is implementation-defined)
496 /// ```
497 pub fn shrink_to_fit(&mut self) {
498 self.slots.shrink_to_fit();
499 self.free_list.shrink_to_fit();
500 }
501
502 /// Clears all entries and shrinks internal storage.
503 ///
504 /// # Example
505 ///
506 /// ```
507 /// use cachekit::ds::SlotArena;
508 ///
509 /// let mut arena = SlotArena::with_capacity(100);
510 /// arena.insert("a");
511 /// arena.insert("b");
512 ///
513 /// arena.clear_shrink();
514 /// assert!(arena.is_empty());
515 /// ```
516 pub fn clear_shrink(&mut self) {
517 self.clear();
518 self.shrink_to_fit();
519 }
520
521 /// Removes all entries and resets internal state.
522 ///
523 /// # Example
524 ///
525 /// ```
526 /// use cachekit::ds::SlotArena;
527 ///
528 /// let mut arena = SlotArena::new();
529 /// let a = arena.insert("a");
530 /// let b = arena.insert("b");
531 ///
532 /// arena.clear();
533 /// assert!(arena.is_empty());
534 /// assert!(!arena.contains(a));
535 /// assert!(!arena.contains(b));
536 /// ```
537 pub fn clear(&mut self) {
538 self.slots.clear();
539 self.free_list.clear();
540 self.len = 0;
541 }
542
543 /// Iterates over live `(SlotId, &T)` pairs.
544 ///
545 /// # Example
546 ///
547 /// ```
548 /// use cachekit::ds::SlotArena;
549 ///
550 /// let mut arena = SlotArena::new();
551 /// arena.insert("a");
552 /// arena.insert("b");
553 ///
554 /// let values: Vec<_> = arena.iter().map(|(_, v)| *v).collect();
555 /// assert_eq!(values, vec!["a", "b"]);
556 /// ```
557 pub fn iter(&self) -> impl Iterator<Item = (SlotId, &T)> {
558 self.slots
559 .iter()
560 .enumerate()
561 .filter_map(|(idx, slot)| slot.as_ref().map(|value| (SlotId(idx), value)))
562 }
563
564 /// Iterates over live [`SlotId`]s only.
565 ///
566 /// # Example
567 ///
568 /// ```
569 /// use cachekit::ds::SlotArena;
570 ///
571 /// let mut arena = SlotArena::new();
572 /// let a = arena.insert("a");
573 /// let b = arena.insert("b");
574 /// arena.remove(a);
575 ///
576 /// let ids: Vec<_> = arena.iter_ids().collect();
577 /// assert_eq!(ids.len(), 1);
578 /// assert!(ids.contains(&b));
579 /// ```
580 pub fn iter_ids(&self) -> impl Iterator<Item = SlotId> + '_ {
581 self.slots
582 .iter()
583 .enumerate()
584 .filter_map(|(idx, slot)| slot.as_ref().map(|_| SlotId(idx)))
585 }
586
587 #[cfg(any(test, debug_assertions))]
588 /// Returns a debug snapshot of arena internals.
589 pub fn debug_snapshot(&self) -> SlotArenaSnapshot {
590 let mut free_list = self.free_list.clone();
591 free_list.sort_unstable();
592 let mut live_ids: Vec<_> = self.iter_ids().collect();
593 live_ids.sort_by_key(|id| id.index());
594 SlotArenaSnapshot {
595 len: self.len,
596 slots_len: self.slots.len(),
597 free_list,
598 live_ids,
599 }
600 }
601
602 /// Returns an approximate memory footprint in bytes.
603 ///
604 /// Includes the struct itself, slot storage, and free list.
605 ///
606 /// # Example
607 ///
608 /// ```
609 /// use cachekit::ds::SlotArena;
610 ///
611 /// let mut arena: SlotArena<u64> = SlotArena::with_capacity(100);
612 /// let bytes = arena.approx_bytes();
613 /// assert!(bytes > 0);
614 /// ```
615 pub fn approx_bytes(&self) -> usize {
616 std::mem::size_of::<Self>()
617 + self.slots.capacity() * std::mem::size_of::<Option<T>>()
618 + self.free_list.capacity() * std::mem::size_of::<usize>()
619 }
620
621 #[cfg(any(test, debug_assertions))]
622 /// Validates internal invariants (debug/test builds only).
623 pub fn debug_validate_invariants(&self) {
624 let live_count = self.slots.iter().filter(|slot| slot.is_some()).count();
625 assert_eq!(self.len, live_count);
626 assert!(self.len <= self.slots.len());
627
628 let mut seen_free = std::collections::HashSet::new();
629 for &idx in &self.free_list {
630 assert!(idx < self.slots.len());
631 assert!(self.slots[idx].is_none());
632 assert!(seen_free.insert(idx));
633 }
634
635 assert_eq!(self.slots.len(), self.free_list.len() + self.len);
636 }
637}
638
639#[cfg(any(test, debug_assertions))]
640#[derive(Debug, Clone, PartialEq, Eq)]
641pub struct SlotArenaSnapshot {
642 pub len: usize,
643 pub slots_len: usize,
644 pub free_list: Vec<usize>,
645 pub live_ids: Vec<SlotId>,
646}
647
648/// Stable handle into a [`ShardedSlotArena`].
649///
650/// Contains both the shard index and the [`SlotId`] within that shard.
651/// Required for O(1) access since the shard must be known to locate the value.
652///
653/// # Example
654///
655/// ```
656/// use cachekit::ds::ShardedSlotArena;
657///
658/// let arena = ShardedSlotArena::new(4);
659/// let id = arena.insert(42);
660///
661/// // Inspect shard and slot
662/// println!("Shard {}, Slot {}", id.shard(), id.slot().index());
663///
664/// // Access value
665/// assert_eq!(arena.get_with(id, |v| *v), Some(42));
666/// ```
667#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
668#[cfg(feature = "concurrency")]
669pub struct ShardedSlotId {
670 shard: usize,
671 slot: SlotId,
672}
673
674#[cfg(feature = "concurrency")]
675impl ShardedSlotId {
676 /// Returns the shard index.
677 ///
678 /// # Example
679 ///
680 /// ```
681 /// use cachekit::ds::ShardedSlotArena;
682 ///
683 /// let arena = ShardedSlotArena::new(4);
684 /// let id = arena.insert(42);
685 /// assert!(id.shard() < 4);
686 /// ```
687 pub fn shard(self) -> usize {
688 self.shard
689 }
690
691 /// Returns the [`SlotId`] within the shard.
692 ///
693 /// # Example
694 ///
695 /// ```
696 /// use cachekit::ds::ShardedSlotArena;
697 ///
698 /// let arena = ShardedSlotArena::new(2);
699 /// let id = arena.insert("value");
700 /// let slot = id.slot();
701 /// // slot.index() is the position within the shard
702 /// ```
703 pub fn slot(self) -> SlotId {
704 self.slot
705 }
706}
707
708/// Thread-safe sharded arena with multiple `RwLock`-protected [`SlotArena`]s.
709///
710/// Distributes inserts across shards in round-robin fashion to reduce
711/// contention. Each shard has its own lock, so operations on different
712/// shards can proceed in parallel.
713///
714/// # Use Cases
715///
716/// - High-concurrency metadata storage
717/// - When a single `ConcurrentSlotArena` becomes a bottleneck
718/// - Workloads with many concurrent writers
719///
720/// # Example
721///
722/// ```
723/// use std::sync::Arc;
724/// use std::thread;
725/// use cachekit::ds::ShardedSlotArena;
726///
727/// let arena = Arc::new(ShardedSlotArena::new(4));
728///
729/// // Spawn writers
730/// let handles: Vec<_> = (0..4).map(|_| {
731/// let arena = Arc::clone(&arena);
732/// thread::spawn(move || {
733/// for i in 0..100 {
734/// arena.insert(i);
735/// }
736/// })
737/// }).collect();
738///
739/// for h in handles {
740/// h.join().unwrap();
741/// }
742///
743/// assert_eq!(arena.len(), 400);
744/// assert_eq!(arena.shard_count(), 4);
745/// ```
746#[cfg(feature = "concurrency")]
747#[derive(Debug)]
748pub struct ShardedSlotArena<T> {
749 shards: Vec<parking_lot::RwLock<SlotArena<T>>>,
750 selector: crate::ds::ShardSelector,
751 next_shard: std::sync::atomic::AtomicUsize,
752}
753
754#[cfg(feature = "concurrency")]
755impl<T> ShardedSlotArena<T> {
756 /// Creates a sharded arena with the specified number of shards.
757 ///
758 /// # Example
759 ///
760 /// ```
761 /// use cachekit::ds::ShardedSlotArena;
762 ///
763 /// let arena: ShardedSlotArena<i32> = ShardedSlotArena::new(8);
764 /// assert_eq!(arena.shard_count(), 8);
765 /// ```
766 pub fn new(shards: usize) -> Self {
767 let shards = shards.max(1);
768 let mut arenas = Vec::with_capacity(shards);
769 for _ in 0..shards {
770 arenas.push(parking_lot::RwLock::new(SlotArena::new()));
771 }
772 Self {
773 shards: arenas,
774 selector: crate::ds::ShardSelector::new(shards, 0),
775 next_shard: std::sync::atomic::AtomicUsize::new(0),
776 }
777 }
778
779 /// Creates a sharded arena with pre-allocated capacity per shard.
780 ///
781 /// # Example
782 ///
783 /// ```
784 /// use cachekit::ds::ShardedSlotArena;
785 ///
786 /// // 4 shards, each pre-allocated for 1000 entries
787 /// let arena: ShardedSlotArena<String> = ShardedSlotArena::with_capacity(4, 1000);
788 /// assert_eq!(arena.shard_count(), 4);
789 /// ```
790 pub fn with_capacity(shards: usize, capacity: usize) -> Self {
791 let shards = shards.max(1);
792 let mut arenas = Vec::with_capacity(shards);
793 for _ in 0..shards {
794 arenas.push(parking_lot::RwLock::new(SlotArena::with_capacity(capacity)));
795 }
796 Self {
797 shards: arenas,
798 selector: crate::ds::ShardSelector::new(shards, 0),
799 next_shard: std::sync::atomic::AtomicUsize::new(0),
800 }
801 }
802
803 /// Alias for [`with_capacity`](Self::with_capacity).
804 pub fn with_shards(shards: usize, capacity_per_shard: usize) -> Self {
805 Self::with_capacity(shards, capacity_per_shard)
806 }
807
808 /// Creates a sharded arena with a custom hash seed for shard selection.
809 ///
810 /// # Example
811 ///
812 /// ```
813 /// use cachekit::ds::ShardedSlotArena;
814 ///
815 /// // Custom seed for deterministic shard selection
816 /// let arena: ShardedSlotArena<i32> = ShardedSlotArena::with_shards_seed(4, 100, 42);
817 /// assert_eq!(arena.shard_count(), 4);
818 /// ```
819 pub fn with_shards_seed(shards: usize, capacity_per_shard: usize, seed: u64) -> Self {
820 let shards = shards.max(1);
821 let mut arenas = Vec::with_capacity(shards);
822 for _ in 0..shards {
823 arenas.push(parking_lot::RwLock::new(SlotArena::with_capacity(
824 capacity_per_shard,
825 )));
826 }
827 Self {
828 shards: arenas,
829 selector: crate::ds::ShardSelector::new(shards, seed),
830 next_shard: std::sync::atomic::AtomicUsize::new(0),
831 }
832 }
833
834 /// Returns the shard index for a key using the configured hash selector.
835 ///
836 /// Useful for co-locating related data in the same shard.
837 ///
838 /// # Example
839 ///
840 /// ```
841 /// use cachekit::ds::ShardedSlotArena;
842 ///
843 /// let arena: ShardedSlotArena<String> = ShardedSlotArena::new(8);
844 /// let shard = arena.shard_for_key(&"my_key");
845 /// assert!(shard < 8);
846 /// ```
847 pub fn shard_for_key<K: std::hash::Hash>(&self, key: &K) -> usize {
848 self.selector.shard_for_key(key)
849 }
850
851 /// Returns the number of shards.
852 ///
853 /// # Example
854 ///
855 /// ```
856 /// use cachekit::ds::ShardedSlotArena;
857 ///
858 /// let arena: ShardedSlotArena<i32> = ShardedSlotArena::new(16);
859 /// assert_eq!(arena.shard_count(), 16);
860 /// ```
861 pub fn shard_count(&self) -> usize {
862 self.shards.len()
863 }
864
865 /// Inserts a value into the next shard (round-robin) and returns its handle.
866 ///
867 /// # Example
868 ///
869 /// ```
870 /// use cachekit::ds::ShardedSlotArena;
871 ///
872 /// let arena = ShardedSlotArena::new(2);
873 /// let a = arena.insert("first");
874 /// let b = arena.insert("second");
875 ///
876 /// // Inserts alternate between shards
877 /// assert_ne!(a.shard(), b.shard());
878 /// ```
879 pub fn insert(&self, value: T) -> ShardedSlotId {
880 let shard = self
881 .next_shard
882 .fetch_add(1, std::sync::atomic::Ordering::Relaxed)
883 % self.shards.len();
884 let mut arena = self.shards[shard].write();
885 let slot = arena.insert(value);
886 ShardedSlotId { shard, slot }
887 }
888
889 /// Removes and returns the value at `id`.
890 ///
891 /// # Example
892 ///
893 /// ```
894 /// use cachekit::ds::ShardedSlotArena;
895 ///
896 /// let arena = ShardedSlotArena::new(2);
897 /// let id = arena.insert(42);
898 ///
899 /// assert_eq!(arena.remove(id), Some(42));
900 /// assert_eq!(arena.remove(id), None); // Already removed
901 /// ```
902 pub fn remove(&self, id: ShardedSlotId) -> Option<T> {
903 let mut arena = self.shards.get(id.shard)?.write();
904 arena.remove(id.slot)
905 }
906
907 /// Runs a closure on a shared reference to the value at `id`.
908 ///
909 /// # Example
910 ///
911 /// ```
912 /// use cachekit::ds::ShardedSlotArena;
913 ///
914 /// let arena = ShardedSlotArena::new(2);
915 /// let id = arena.insert(vec![1, 2, 3]);
916 ///
917 /// let len = arena.get_with(id, |v| v.len());
918 /// assert_eq!(len, Some(3));
919 /// ```
920 pub fn get_with<R>(&self, id: ShardedSlotId, f: impl FnOnce(&T) -> R) -> Option<R> {
921 let arena = self.shards.get(id.shard)?.read();
922 arena.get(id.slot).map(f)
923 }
924
925 /// Runs a closure on a mutable reference to the value at `id`.
926 ///
927 /// # Example
928 ///
929 /// ```
930 /// use cachekit::ds::ShardedSlotArena;
931 ///
932 /// let arena = ShardedSlotArena::new(2);
933 /// let id = arena.insert(10);
934 ///
935 /// arena.get_mut_with(id, |v| *v += 5);
936 /// assert_eq!(arena.get_with(id, |v| *v), Some(15));
937 /// ```
938 pub fn get_mut_with<R>(&self, id: ShardedSlotId, f: impl FnOnce(&mut T) -> R) -> Option<R> {
939 let mut arena = self.shards.get(id.shard)?.write();
940 arena.get_mut(id.slot).map(f)
941 }
942
943 /// Returns `true` if `id` refers to a live slot.
944 ///
945 /// # Example
946 ///
947 /// ```
948 /// use cachekit::ds::ShardedSlotArena;
949 ///
950 /// let arena = ShardedSlotArena::new(2);
951 /// let id = arena.insert("value");
952 ///
953 /// assert!(arena.contains(id));
954 /// arena.remove(id);
955 /// assert!(!arena.contains(id));
956 /// ```
957 pub fn contains(&self, id: ShardedSlotId) -> bool {
958 self.shards
959 .get(id.shard)
960 .map(|arena| arena.read().contains(id.slot))
961 .unwrap_or(false)
962 }
963
964 /// Returns the total number of live entries across all shards.
965 ///
966 /// # Example
967 ///
968 /// ```
969 /// use cachekit::ds::ShardedSlotArena;
970 ///
971 /// let arena = ShardedSlotArena::new(4);
972 /// arena.insert(1);
973 /// arena.insert(2);
974 /// arena.insert(3);
975 ///
976 /// assert_eq!(arena.len(), 3);
977 /// ```
978 pub fn len(&self) -> usize {
979 self.shards.iter().map(|arena| arena.read().len()).sum()
980 }
981
982 /// Returns `true` if all shards are empty.
983 ///
984 /// # Example
985 ///
986 /// ```
987 /// use cachekit::ds::ShardedSlotArena;
988 ///
989 /// let arena: ShardedSlotArena<i32> = ShardedSlotArena::new(2);
990 /// assert!(arena.is_empty());
991 ///
992 /// arena.insert(1);
993 /// assert!(!arena.is_empty());
994 /// ```
995 pub fn is_empty(&self) -> bool {
996 self.len() == 0
997 }
998
999 /// Reserves capacity in each shard.
1000 ///
1001 /// # Example
1002 ///
1003 /// ```
1004 /// use cachekit::ds::ShardedSlotArena;
1005 ///
1006 /// let arena: ShardedSlotArena<i32> = ShardedSlotArena::new(4);
1007 /// arena.reserve_slots(100); // Each shard gets 100 additional capacity
1008 /// ```
1009 pub fn reserve_slots(&self, additional: usize) {
1010 for arena in &self.shards {
1011 arena.write().reserve_slots(additional);
1012 }
1013 }
1014
1015 /// Shrinks all shards to fit their current state.
1016 ///
1017 /// # Example
1018 ///
1019 /// ```
1020 /// use cachekit::ds::ShardedSlotArena;
1021 ///
1022 /// let arena: ShardedSlotArena<i32> = ShardedSlotArena::with_capacity(4, 1000);
1023 /// arena.insert(1);
1024 /// arena.shrink_to_fit();
1025 /// ```
1026 pub fn shrink_to_fit(&self) {
1027 for arena in &self.shards {
1028 arena.write().shrink_to_fit();
1029 }
1030 }
1031
1032 /// Clears all shards and shrinks storage.
1033 ///
1034 /// # Example
1035 ///
1036 /// ```
1037 /// use cachekit::ds::ShardedSlotArena;
1038 ///
1039 /// let arena = ShardedSlotArena::new(2);
1040 /// arena.insert(1);
1041 /// arena.insert(2);
1042 ///
1043 /// arena.clear_shrink();
1044 /// assert!(arena.is_empty());
1045 /// ```
1046 pub fn clear_shrink(&self) {
1047 for arena in &self.shards {
1048 arena.write().clear_shrink();
1049 }
1050 }
1051
1052 /// Returns approximate memory footprint across all shards.
1053 ///
1054 /// # Example
1055 ///
1056 /// ```
1057 /// use cachekit::ds::ShardedSlotArena;
1058 ///
1059 /// let arena: ShardedSlotArena<u64> = ShardedSlotArena::with_capacity(4, 100);
1060 /// let bytes = arena.approx_bytes();
1061 /// assert!(bytes > 0);
1062 /// ```
1063 pub fn approx_bytes(&self) -> usize {
1064 self.shards
1065 .iter()
1066 .map(|arena| arena.read().approx_bytes())
1067 .sum()
1068 }
1069}
1070
1071impl<T> Default for SlotArena<T> {
1072 fn default() -> Self {
1073 Self::new()
1074 }
1075}
1076
1077/// Thread-safe [`SlotArena`] wrapper using `parking_lot::RwLock`.
1078///
1079/// Provides the same functionality as [`SlotArena`] but safe for concurrent
1080/// access. Uses closure-based access (`get_with`, `get_mut_with`) since
1081/// references cannot outlive lock guards.
1082///
1083/// For high-contention workloads, consider [`ShardedSlotArena`] instead.
1084///
1085/// # Example
1086///
1087/// ```
1088/// use std::sync::Arc;
1089/// use std::thread;
1090/// use cachekit::ds::ConcurrentSlotArena;
1091///
1092/// let arena = Arc::new(ConcurrentSlotArena::new());
1093///
1094/// // Insert from main thread
1095/// let id = arena.insert(0);
1096///
1097/// // Spawn readers and writers
1098/// let handles: Vec<_> = (0..4).map(|i| {
1099/// let arena = Arc::clone(&arena);
1100/// thread::spawn(move || {
1101/// // Read
1102/// let val = arena.get_with(id, |v| *v);
1103///
1104/// // Write (increment)
1105/// arena.get_mut_with(id, |v| *v += 1);
1106/// })
1107/// }).collect();
1108///
1109/// for h in handles {
1110/// h.join().unwrap();
1111/// }
1112///
1113/// assert_eq!(arena.get_with(id, |v| *v), Some(4));
1114/// ```
1115#[cfg(feature = "concurrency")]
1116#[derive(Debug)]
1117pub struct ConcurrentSlotArena<T> {
1118 inner: parking_lot::RwLock<SlotArena<T>>,
1119}
1120
1121#[cfg(feature = "concurrency")]
1122impl<T> ConcurrentSlotArena<T> {
1123 /// Creates an empty concurrent arena.
1124 ///
1125 /// # Example
1126 ///
1127 /// ```
1128 /// use cachekit::ds::ConcurrentSlotArena;
1129 ///
1130 /// let arena: ConcurrentSlotArena<String> = ConcurrentSlotArena::new();
1131 /// assert!(arena.is_empty());
1132 /// ```
1133 pub fn new() -> Self {
1134 Self {
1135 inner: parking_lot::RwLock::new(SlotArena::new()),
1136 }
1137 }
1138
1139 /// Creates a concurrent arena with pre-allocated capacity.
1140 ///
1141 /// # Example
1142 ///
1143 /// ```
1144 /// use cachekit::ds::ConcurrentSlotArena;
1145 ///
1146 /// let arena: ConcurrentSlotArena<i32> = ConcurrentSlotArena::with_capacity(1000);
1147 /// assert!(arena.capacity() >= 1000);
1148 /// ```
1149 pub fn with_capacity(capacity: usize) -> Self {
1150 Self {
1151 inner: parking_lot::RwLock::new(SlotArena::with_capacity(capacity)),
1152 }
1153 }
1154
1155 /// Inserts a value and returns its [`SlotId`].
1156 ///
1157 /// Acquires write lock.
1158 ///
1159 /// # Example
1160 ///
1161 /// ```
1162 /// use cachekit::ds::ConcurrentSlotArena;
1163 ///
1164 /// let arena = ConcurrentSlotArena::new();
1165 /// let id = arena.insert(42);
1166 /// assert!(arena.contains(id));
1167 /// ```
1168 pub fn insert(&self, value: T) -> SlotId {
1169 let mut arena = self.inner.write();
1170 arena.insert(value)
1171 }
1172
1173 /// Removes and returns the value at `id`.
1174 ///
1175 /// Acquires write lock.
1176 ///
1177 /// # Example
1178 ///
1179 /// ```
1180 /// use cachekit::ds::ConcurrentSlotArena;
1181 ///
1182 /// let arena = ConcurrentSlotArena::new();
1183 /// let id = arena.insert(42);
1184 ///
1185 /// assert_eq!(arena.remove(id), Some(42));
1186 /// assert_eq!(arena.remove(id), None);
1187 /// ```
1188 pub fn remove(&self, id: SlotId) -> Option<T> {
1189 let mut arena = self.inner.write();
1190 arena.remove(id)
1191 }
1192
1193 /// Runs a closure on a shared reference to the value at `id`.
1194 ///
1195 /// Acquires read lock. The closure's return value is returned.
1196 ///
1197 /// # Example
1198 ///
1199 /// ```
1200 /// use cachekit::ds::ConcurrentSlotArena;
1201 ///
1202 /// let arena = ConcurrentSlotArena::new();
1203 /// let id = arena.insert(vec![1, 2, 3]);
1204 ///
1205 /// let sum = arena.get_with(id, |v| v.iter().sum::<i32>());
1206 /// assert_eq!(sum, Some(6));
1207 /// ```
1208 pub fn get_with<R>(&self, id: SlotId, f: impl FnOnce(&T) -> R) -> Option<R> {
1209 let arena = self.inner.read();
1210 arena.get(id).map(f)
1211 }
1212
1213 /// Non-blocking version of [`get_with`](Self::get_with).
1214 ///
1215 /// Returns `None` if the lock cannot be acquired immediately.
1216 ///
1217 /// # Example
1218 ///
1219 /// ```
1220 /// use cachekit::ds::ConcurrentSlotArena;
1221 ///
1222 /// let arena = ConcurrentSlotArena::new();
1223 /// let id = arena.insert(100);
1224 ///
1225 /// // Returns Some if lock is available
1226 /// if let Some(val) = arena.try_get_with(id, |v| *v) {
1227 /// assert_eq!(val, 100);
1228 /// }
1229 /// ```
1230 pub fn try_get_with<R>(&self, id: SlotId, f: impl FnOnce(&T) -> R) -> Option<R> {
1231 let arena = self.inner.try_read()?;
1232 arena.get(id).map(f)
1233 }
1234
1235 /// Runs a closure on a mutable reference to the value at `id`.
1236 ///
1237 /// Acquires write lock.
1238 ///
1239 /// # Example
1240 ///
1241 /// ```
1242 /// use cachekit::ds::ConcurrentSlotArena;
1243 ///
1244 /// let arena = ConcurrentSlotArena::new();
1245 /// let id = arena.insert(1);
1246 ///
1247 /// arena.get_mut_with(id, |v| *v = 2);
1248 /// assert_eq!(arena.get_with(id, |v| *v), Some(2));
1249 /// ```
1250 pub fn get_mut_with<R>(&self, id: SlotId, f: impl FnOnce(&mut T) -> R) -> Option<R> {
1251 let mut arena = self.inner.write();
1252 arena.get_mut(id).map(f)
1253 }
1254
1255 /// Non-blocking version of [`get_mut_with`](Self::get_mut_with).
1256 ///
1257 /// Returns `None` if the lock cannot be acquired immediately.
1258 ///
1259 /// # Example
1260 ///
1261 /// ```
1262 /// use cachekit::ds::ConcurrentSlotArena;
1263 ///
1264 /// let arena = ConcurrentSlotArena::new();
1265 /// let id = arena.insert(5);
1266 ///
1267 /// // Attempt non-blocking write
1268 /// if arena.try_get_mut_with(id, |v| *v += 1).is_some() {
1269 /// assert_eq!(arena.get_with(id, |v| *v), Some(6));
1270 /// }
1271 /// ```
1272 pub fn try_get_mut_with<R>(&self, id: SlotId, f: impl FnOnce(&mut T) -> R) -> Option<R> {
1273 let mut arena = self.inner.try_write()?;
1274 arena.get_mut(id).map(f)
1275 }
1276
1277 /// Returns `true` if `id` refers to a live slot.
1278 ///
1279 /// # Example
1280 ///
1281 /// ```
1282 /// use cachekit::ds::ConcurrentSlotArena;
1283 ///
1284 /// let arena = ConcurrentSlotArena::new();
1285 /// let id = arena.insert("value");
1286 ///
1287 /// assert!(arena.contains(id));
1288 /// arena.remove(id);
1289 /// assert!(!arena.contains(id));
1290 /// ```
1291 pub fn contains(&self, id: SlotId) -> bool {
1292 let arena = self.inner.read();
1293 arena.contains(id)
1294 }
1295
1296 /// Returns the number of live entries.
1297 ///
1298 /// # Example
1299 ///
1300 /// ```
1301 /// use cachekit::ds::ConcurrentSlotArena;
1302 ///
1303 /// let arena = ConcurrentSlotArena::new();
1304 /// assert_eq!(arena.len(), 0);
1305 ///
1306 /// arena.insert(1);
1307 /// arena.insert(2);
1308 /// assert_eq!(arena.len(), 2);
1309 /// ```
1310 pub fn len(&self) -> usize {
1311 let arena = self.inner.read();
1312 arena.len()
1313 }
1314
1315 /// Returns `true` if there are no live entries.
1316 ///
1317 /// # Example
1318 ///
1319 /// ```
1320 /// use cachekit::ds::ConcurrentSlotArena;
1321 ///
1322 /// let arena: ConcurrentSlotArena<i32> = ConcurrentSlotArena::new();
1323 /// assert!(arena.is_empty());
1324 ///
1325 /// let id = arena.insert(1);
1326 /// assert!(!arena.is_empty());
1327 /// ```
1328 pub fn is_empty(&self) -> bool {
1329 let arena = self.inner.read();
1330 arena.is_empty()
1331 }
1332
1333 /// Returns the backing Vec capacity.
1334 ///
1335 /// # Example
1336 ///
1337 /// ```
1338 /// use cachekit::ds::ConcurrentSlotArena;
1339 ///
1340 /// let arena: ConcurrentSlotArena<i32> = ConcurrentSlotArena::with_capacity(100);
1341 /// assert!(arena.capacity() >= 100);
1342 /// ```
1343 pub fn capacity(&self) -> usize {
1344 let arena = self.inner.read();
1345 arena.capacity()
1346 }
1347
1348 /// Reserves capacity for additional slots.
1349 ///
1350 /// # Example
1351 ///
1352 /// ```
1353 /// use cachekit::ds::ConcurrentSlotArena;
1354 ///
1355 /// let arena: ConcurrentSlotArena<i32> = ConcurrentSlotArena::new();
1356 /// arena.reserve_slots(100);
1357 /// assert!(arena.capacity() >= 100);
1358 /// ```
1359 pub fn reserve_slots(&self, additional: usize) {
1360 let mut arena = self.inner.write();
1361 arena.reserve_slots(additional);
1362 }
1363
1364 /// Shrinks internal storage to fit current state.
1365 ///
1366 /// # Example
1367 ///
1368 /// ```
1369 /// use cachekit::ds::ConcurrentSlotArena;
1370 ///
1371 /// let arena: ConcurrentSlotArena<i32> = ConcurrentSlotArena::with_capacity(1000);
1372 /// arena.insert(1);
1373 /// arena.shrink_to_fit();
1374 /// ```
1375 pub fn shrink_to_fit(&self) {
1376 let mut arena = self.inner.write();
1377 arena.shrink_to_fit();
1378 }
1379
1380 /// Clears all entries and shrinks storage.
1381 ///
1382 /// # Example
1383 ///
1384 /// ```
1385 /// use cachekit::ds::ConcurrentSlotArena;
1386 ///
1387 /// let arena = ConcurrentSlotArena::with_capacity(100);
1388 /// arena.insert("a");
1389 /// arena.insert("b");
1390 ///
1391 /// arena.clear_shrink();
1392 /// assert!(arena.is_empty());
1393 /// ```
1394 pub fn clear_shrink(&self) {
1395 let mut arena = self.inner.write();
1396 arena.clear_shrink();
1397 }
1398
1399 /// Clears all entries.
1400 ///
1401 /// # Example
1402 ///
1403 /// ```
1404 /// use cachekit::ds::ConcurrentSlotArena;
1405 ///
1406 /// let arena = ConcurrentSlotArena::new();
1407 /// let a = arena.insert(1);
1408 /// let b = arena.insert(2);
1409 ///
1410 /// arena.clear();
1411 /// assert!(arena.is_empty());
1412 /// assert!(!arena.contains(a));
1413 /// ```
1414 pub fn clear(&self) {
1415 let mut arena = self.inner.write();
1416 arena.clear();
1417 }
1418
1419 /// Returns approximate memory footprint in bytes.
1420 ///
1421 /// # Example
1422 ///
1423 /// ```
1424 /// use cachekit::ds::ConcurrentSlotArena;
1425 ///
1426 /// let arena: ConcurrentSlotArena<u64> = ConcurrentSlotArena::with_capacity(100);
1427 /// let bytes = arena.approx_bytes();
1428 /// assert!(bytes > 0);
1429 /// ```
1430 pub fn approx_bytes(&self) -> usize {
1431 let arena = self.inner.read();
1432 arena.approx_bytes()
1433 }
1434}
1435
1436#[cfg(feature = "concurrency")]
1437impl<T> Default for ConcurrentSlotArena<T> {
1438 fn default() -> Self {
1439 Self::new()
1440 }
1441}
1442
1443#[cfg(test)]
1444mod tests {
1445 use super::*;
1446
1447 #[test]
1448 fn slot_arena_insert_remove_reuse() {
1449 let mut arena = SlotArena::new();
1450 let id1 = arena.insert("a");
1451 let id2 = arena.insert("b");
1452 assert_eq!(arena.len(), 2);
1453 assert_eq!(arena.get(id1), Some(&"a"));
1454 assert_eq!(arena.get(id2), Some(&"b"));
1455
1456 assert_eq!(arena.remove(id1), Some("a"));
1457 assert_eq!(arena.len(), 1);
1458
1459 let id3 = arena.insert("c");
1460 assert_eq!(arena.len(), 2);
1461 assert_eq!(arena.get(id3), Some(&"c"));
1462 assert_eq!(id1.index(), id3.index());
1463 }
1464
1465 #[cfg(feature = "concurrency")]
1466 #[test]
1467 fn concurrent_slot_arena_basic_ops() {
1468 let arena = ConcurrentSlotArena::new();
1469 let id = arena.insert(10);
1470 assert_eq!(arena.get_with(id, |v| *v), Some(10));
1471 assert!(arena.contains(id));
1472 assert_eq!(arena.len(), 1);
1473
1474 arena.get_mut_with(id, |v| *v = 20);
1475 assert_eq!(arena.get_with(id, |v| *v), Some(20));
1476 assert_eq!(arena.remove(id), Some(20));
1477 assert!(!arena.contains(id));
1478 assert!(arena.is_empty());
1479 }
1480
1481 #[test]
1482 fn slot_arena_remove_invalid_id_is_none() {
1483 let mut arena: SlotArena<i32> = SlotArena::new();
1484 let id = SlotId(0);
1485 assert_eq!(arena.remove(id), None);
1486 assert!(!arena.contains(id));
1487 assert!(arena.is_empty());
1488 }
1489
1490 #[test]
1491 fn slot_arena_clear_resets_state() {
1492 let mut arena = SlotArena::with_capacity(4);
1493 let a = arena.insert("a");
1494 let b = arena.insert("b");
1495 assert_eq!(arena.len(), 2);
1496 assert!(arena.contains(a));
1497 assert!(arena.contains(b));
1498
1499 arena.clear();
1500 assert_eq!(arena.len(), 0);
1501 assert!(arena.is_empty());
1502 assert!(!arena.contains(a));
1503 assert!(!arena.contains(b));
1504 assert_eq!(arena.iter().count(), 0);
1505 }
1506
1507 #[test]
1508 fn slot_arena_iter_skips_empty_slots() {
1509 let mut arena = SlotArena::new();
1510 let a = arena.insert("a");
1511 let b = arena.insert("b");
1512 let c = arena.insert("c");
1513 assert_eq!(arena.remove(b), Some("b"));
1514
1515 let mut values: Vec<_> = arena.iter().map(|(_, v)| *v).collect();
1516 values.sort();
1517 assert_eq!(values, vec!["a", "c"]);
1518 assert!(arena.contains(a));
1519 assert!(arena.contains(c));
1520 }
1521
1522 #[test]
1523 fn slot_arena_get_mut_updates_value() {
1524 let mut arena = SlotArena::new();
1525 let id = arena.insert(1);
1526 if let Some(value) = arena.get_mut(id) {
1527 *value = 2;
1528 }
1529 assert_eq!(arena.get(id), Some(&2));
1530 }
1531
1532 #[test]
1533 fn slot_arena_capacity_tracking() {
1534 let arena: SlotArena<i32> = SlotArena::with_capacity(16);
1535 assert!(arena.capacity() >= 16);
1536 assert_eq!(arena.len(), 0);
1537 }
1538
1539 #[test]
1540 fn slot_arena_contains_index_and_iter_ids() {
1541 let mut arena = SlotArena::new();
1542 let a = arena.insert("a");
1543 let b = arena.insert("b");
1544 assert!(arena.contains_index(a.index()));
1545 assert!(arena.contains_index(b.index()));
1546 arena.remove(a);
1547 assert!(!arena.contains_index(a.index()));
1548
1549 let ids: Vec<_> = arena.iter_ids().collect();
1550 assert_eq!(ids, vec![b]);
1551 }
1552
1553 #[test]
1554 fn slot_arena_reserve_slots_grows_capacity() {
1555 let mut arena: SlotArena<i32> = SlotArena::new();
1556 let before = arena.capacity();
1557 arena.reserve_slots(32);
1558 assert!(arena.capacity() >= before + 32);
1559 }
1560
1561 #[test]
1562 fn slot_arena_debug_snapshot() {
1563 let mut arena = SlotArena::new();
1564 let a = arena.insert("a");
1565 let b = arena.insert("b");
1566 arena.remove(a);
1567 let snapshot = arena.debug_snapshot();
1568 assert_eq!(snapshot.len, 1);
1569 assert!(snapshot.live_ids.contains(&b));
1570 assert!(snapshot.free_list.contains(&a.index()));
1571 }
1572
1573 #[cfg(feature = "concurrency")]
1574 #[test]
1575 fn sharded_slot_arena_basic_ops() {
1576 let arena = ShardedSlotArena::new(2);
1577 let a = arena.insert(1);
1578 let b = arena.insert(2);
1579 assert!(arena.contains(a));
1580 assert!(arena.contains(b));
1581 assert_eq!(arena.get_with(a, |v| *v), Some(1));
1582 assert_eq!(arena.remove(b), Some(2));
1583 assert!(!arena.contains(b));
1584 assert_eq!(arena.len(), 1);
1585 }
1586
1587 #[cfg(feature = "concurrency")]
1588 #[test]
1589 fn sharded_slot_arena_shard_for_key() {
1590 let arena: ShardedSlotArena<i32> = ShardedSlotArena::new(4);
1591 let shard = arena.shard_for_key(&"alpha");
1592 assert!(shard < arena.shard_count());
1593 }
1594
1595 #[cfg(feature = "concurrency")]
1596 #[test]
1597 fn sharded_slot_arena_with_seed() {
1598 let arena: ShardedSlotArena<i32> = ShardedSlotArena::with_shards_seed(4, 0, 99);
1599 let shard = arena.shard_for_key(&"alpha");
1600 assert!(shard < arena.shard_count());
1601 }
1602
1603 #[cfg(feature = "concurrency")]
1604 #[test]
1605 fn concurrent_slot_arena_try_ops() {
1606 let arena = ConcurrentSlotArena::new();
1607 let id = arena.insert(1);
1608 assert_eq!(arena.try_get_with(id, |v| *v), Some(1));
1609 arena.try_get_mut_with(id, |v| *v = 2);
1610 assert_eq!(arena.get_with(id, |v| *v), Some(2));
1611 }
1612
1613 #[cfg(feature = "concurrency")]
1614 #[test]
1615 fn concurrent_slot_arena_clear_and_accessors() {
1616 let arena = ConcurrentSlotArena::new();
1617 let a = arena.insert(1);
1618 let b = arena.insert(2);
1619 assert_eq!(arena.get_with(a, |v| *v), Some(1));
1620 assert_eq!(arena.get_with(b, |v| *v), Some(2));
1621
1622 arena.clear();
1623 assert!(arena.is_empty());
1624 assert!(!arena.contains(a));
1625 assert!(!arena.contains(b));
1626 assert_eq!(arena.get_with(a, |v| *v), None);
1627 }
1628
1629 #[cfg(feature = "concurrency")]
1630 #[test]
1631 fn concurrent_slot_arena_get_mut_with_updates_value() {
1632 let arena = ConcurrentSlotArena::new();
1633 let id = arena.insert(5);
1634 arena.get_mut_with(id, |v| *v = 10);
1635 assert_eq!(arena.get_with(id, |v| *v), Some(10));
1636 }
1637
1638 #[test]
1639 fn slot_arena_debug_invariants_hold() {
1640 let mut arena = SlotArena::new();
1641 let a = arena.insert(1);
1642 let b = arena.insert(2);
1643 arena.remove(a);
1644 let _ = arena.insert(3);
1645 assert!(arena.contains(b));
1646 arena.debug_validate_invariants();
1647 }
1648}
1649
1650#[cfg(test)]
1651mod property_tests {
1652 use super::*;
1653 use proptest::prelude::*;
1654
1655 // =============================================================================
1656 // Property Tests - Insert Operations
1657 // =============================================================================
1658
1659 proptest! {
1660 /// Property: insert returns valid SlotId
1661 #[cfg_attr(miri, ignore)]
1662 #[test]
1663 fn prop_insert_returns_valid_id(
1664 values in prop::collection::vec(any::<u32>(), 0..50)
1665 ) {
1666 let mut arena = SlotArena::new();
1667
1668 for value in values {
1669 let id = arena.insert(value);
1670
1671 prop_assert_eq!(arena.get(id), Some(&value));
1672 prop_assert!(arena.contains(id));
1673 }
1674 }
1675
1676 /// Property: inserted values can be retrieved
1677 #[cfg_attr(miri, ignore)]
1678 #[test]
1679 fn prop_insert_and_get(
1680 value in any::<u32>()
1681 ) {
1682 let mut arena = SlotArena::new();
1683 let id = arena.insert(value);
1684
1685 prop_assert_eq!(arena.get(id), Some(&value));
1686 }
1687 }
1688
1689 // =============================================================================
1690 // Property Tests - Remove Operations
1691 // =============================================================================
1692
1693 proptest! {
1694 /// Property: remove decreases length by 1
1695 #[cfg_attr(miri, ignore)]
1696 #[test]
1697 fn prop_remove_decreases_length(
1698 values in prop::collection::vec(any::<u32>(), 1..30)
1699 ) {
1700 let mut arena = SlotArena::new();
1701 let mut ids = Vec::new();
1702
1703 for value in &values {
1704 let id = arena.insert(*value);
1705 ids.push(id);
1706 }
1707
1708 for id in ids {
1709 let old_len = arena.len();
1710 let removed = arena.remove(id);
1711
1712 prop_assert!(removed.is_some());
1713 prop_assert_eq!(arena.len(), old_len - 1);
1714 prop_assert!(!arena.contains(id));
1715 }
1716
1717 prop_assert!(arena.is_empty());
1718 }
1719
1720 /// Property: remove returns the correct value
1721 #[cfg_attr(miri, ignore)]
1722 #[test]
1723 fn prop_remove_returns_value(
1724 values in prop::collection::vec(any::<u32>(), 1..30)
1725 ) {
1726 let mut arena = SlotArena::new();
1727 let mut ids = Vec::new();
1728
1729 for value in &values {
1730 let id = arena.insert(*value);
1731 ids.push((id, *value));
1732 }
1733
1734 for (id, expected_value) in ids {
1735 let removed = arena.remove(id);
1736 prop_assert_eq!(removed, Some(expected_value));
1737 }
1738 }
1739
1740 /// Property: removing twice returns None
1741 #[cfg_attr(miri, ignore)]
1742 #[test]
1743 fn prop_remove_twice_returns_none(
1744 value in any::<u32>()
1745 ) {
1746 let mut arena = SlotArena::new();
1747 let id = arena.insert(value);
1748
1749 let first = arena.remove(id);
1750 let second = arena.remove(id);
1751
1752 prop_assert_eq!(first, Some(value));
1753 prop_assert_eq!(second, None);
1754 }
1755 }
1756
1757 // =============================================================================
1758 // Property Tests - SlotId Stability
1759 // =============================================================================
1760
1761 proptest! {
1762 /// Property: SlotId remains valid until removed
1763 #[cfg_attr(miri, ignore)]
1764 #[test]
1765 fn prop_slot_id_stable_until_removed(
1766 values in prop::collection::vec(any::<u32>(), 1..30)
1767 ) {
1768 let mut arena = SlotArena::new();
1769 let mut ids = Vec::new();
1770
1771 for value in &values {
1772 let id = arena.insert(*value);
1773 ids.push((id, *value));
1774 }
1775
1776 // All SlotIds should be valid
1777 for (id, value) in &ids {
1778 prop_assert_eq!(arena.get(*id), Some(value));
1779 prop_assert!(arena.contains(*id));
1780 }
1781
1782 // Remove every other slot
1783 for (idx, (id, _value)) in ids.iter().enumerate() {
1784 if idx % 2 == 0 {
1785 arena.remove(*id);
1786 }
1787 }
1788
1789 // Remaining slots should still be valid
1790 for (idx, (id, value)) in ids.iter().enumerate() {
1791 if idx % 2 != 0 {
1792 prop_assert_eq!(arena.get(*id), Some(value));
1793 } else {
1794 prop_assert_eq!(arena.get(*id), None);
1795 }
1796 }
1797 }
1798 }
1799
1800 // =============================================================================
1801 // Property Tests - Free Slot Reuse
1802 // =============================================================================
1803
1804 proptest! {
1805 /// Property: Freed slots are reused
1806 #[cfg_attr(miri, ignore)]
1807 #[test]
1808 fn prop_free_slot_reuse(
1809 values1 in prop::collection::vec(any::<u32>(), 5..20),
1810 values2 in prop::collection::vec(any::<u32>(), 5..20)
1811 ) {
1812 let mut arena = SlotArena::new();
1813
1814 // Insert and collect indices
1815 let mut ids = Vec::new();
1816 for value in &values1 {
1817 let id = arena.insert(*value);
1818 ids.push(id);
1819 }
1820
1821 let removed_indices: Vec<_> = ids.iter().map(|id| id.index()).collect();
1822
1823 // Remove all slots
1824 for id in ids {
1825 arena.remove(id);
1826 }
1827
1828 prop_assert!(arena.is_empty());
1829
1830 // Insert new values - should reuse freed slots
1831 let mut reused_count = 0;
1832 for value in &values2 {
1833 let id = arena.insert(*value);
1834 if removed_indices.contains(&id.index()) {
1835 reused_count += 1;
1836 }
1837 }
1838
1839 // At least some slots should have been reused
1840 prop_assert!(reused_count > 0);
1841 }
1842 }
1843
1844 // =============================================================================
1845 // Property Tests - Length Tracking
1846 // =============================================================================
1847
1848 proptest! {
1849 /// Property: len() tracks number of live entries
1850 #[cfg_attr(miri, ignore)]
1851 #[test]
1852 fn prop_len_tracks_entries(
1853 values in prop::collection::vec(any::<u32>(), 0..50)
1854 ) {
1855 let mut arena = SlotArena::new();
1856
1857 for (idx, value) in values.iter().enumerate() {
1858 arena.insert(*value);
1859 prop_assert_eq!(arena.len(), idx + 1);
1860 }
1861
1862 let total = values.len();
1863 let mut ids: Vec<_> = arena.iter_ids().collect();
1864
1865 for (idx, id) in ids.drain(..).enumerate() {
1866 arena.remove(id);
1867 prop_assert_eq!(arena.len(), total - idx - 1);
1868 }
1869
1870 prop_assert_eq!(arena.len(), 0);
1871 }
1872
1873 /// Property: is_empty is consistent with len
1874 #[cfg_attr(miri, ignore)]
1875 #[test]
1876 fn prop_is_empty_consistent(
1877 values in prop::collection::vec(any::<u32>(), 0..30)
1878 ) {
1879 let mut arena = SlotArena::new();
1880
1881 for value in values {
1882 arena.insert(value);
1883
1884 if arena.is_empty() {
1885 prop_assert_eq!(arena.len(), 0);
1886 } else {
1887 prop_assert!(!arena.is_empty());
1888 }
1889 }
1890 }
1891 }
1892
1893 // =============================================================================
1894 // Property Tests - Get and Contains
1895 // =============================================================================
1896
1897 proptest! {
1898 /// Property: get returns Some for all inserted values
1899 #[cfg_attr(miri, ignore)]
1900 #[test]
1901 fn prop_get_returns_inserted_values(
1902 values in prop::collection::vec(any::<u32>(), 1..30)
1903 ) {
1904 let mut arena = SlotArena::new();
1905 let mut ids = Vec::new();
1906
1907 for value in &values {
1908 let id = arena.insert(*value);
1909 ids.push((id, *value));
1910 }
1911
1912 for (id, value) in ids {
1913 prop_assert_eq!(arena.get(id), Some(&value));
1914 }
1915 }
1916
1917 /// Property: contains is consistent with get
1918 #[cfg_attr(miri, ignore)]
1919 #[test]
1920 fn prop_contains_consistent_with_get(
1921 values in prop::collection::vec(any::<u32>(), 1..30)
1922 ) {
1923 let mut arena = SlotArena::new();
1924 let mut ids = Vec::new();
1925
1926 for value in &values {
1927 let id = arena.insert(*value);
1928 ids.push(id);
1929 }
1930
1931 for id in ids {
1932 let contains = arena.contains(id);
1933 let get_result = arena.get(id);
1934
1935 if contains {
1936 prop_assert!(get_result.is_some());
1937 } else {
1938 prop_assert!(get_result.is_none());
1939 }
1940 }
1941 }
1942 }
1943
1944 // =============================================================================
1945 // Property Tests - Get Mut
1946 // =============================================================================
1947
1948 proptest! {
1949 /// Property: get_mut allows modification
1950 #[cfg_attr(miri, ignore)]
1951 #[test]
1952 fn prop_get_mut_modifies(
1953 value1 in any::<u32>(),
1954 value2 in any::<u32>()
1955 ) {
1956 let mut arena = SlotArena::new();
1957 let id = arena.insert(value1);
1958
1959 if let Some(val) = arena.get_mut(id) {
1960 *val = value2;
1961 }
1962
1963 prop_assert_eq!(arena.get(id), Some(&value2));
1964 }
1965 }
1966
1967 // =============================================================================
1968 // Property Tests - Iterator
1969 // =============================================================================
1970
1971 proptest! {
1972 /// Property: iter returns exactly len() items
1973 #[cfg_attr(miri, ignore)]
1974 #[test]
1975 fn prop_iter_returns_len_items(
1976 values in prop::collection::vec(any::<u32>(), 0..50)
1977 ) {
1978 let mut arena = SlotArena::new();
1979
1980 for value in values {
1981 arena.insert(value);
1982 }
1983
1984 let iter_count = arena.iter().count();
1985 prop_assert_eq!(iter_count, arena.len());
1986 }
1987
1988 /// Property: iter_ids returns exactly len() items
1989 #[cfg_attr(miri, ignore)]
1990 #[test]
1991 fn prop_iter_ids_returns_len_items(
1992 values in prop::collection::vec(any::<u32>(), 0..50)
1993 ) {
1994 let mut arena = SlotArena::new();
1995
1996 for value in values {
1997 arena.insert(value);
1998 }
1999
2000 let ids_count = arena.iter_ids().count();
2001 prop_assert_eq!(ids_count, arena.len());
2002 }
2003
2004 /// Property: iter skips removed slots
2005 #[cfg_attr(miri, ignore)]
2006 #[test]
2007 fn prop_iter_skips_removed(
2008 values in prop::collection::vec(any::<u32>(), 5..30)
2009 ) {
2010 let mut arena = SlotArena::new();
2011 let mut ids = Vec::new();
2012
2013 for value in &values {
2014 let id = arena.insert(*value);
2015 ids.push(id);
2016 }
2017
2018 // Remove every other slot
2019 for (idx, id) in ids.iter().enumerate() {
2020 if idx % 2 == 0 {
2021 arena.remove(*id);
2022 }
2023 }
2024
2025 let iter_count = arena.iter().count();
2026 let expected_count = values.len() / 2;
2027 prop_assert_eq!(iter_count, expected_count);
2028 }
2029 }
2030
2031 // =============================================================================
2032 // Property Tests - Clear Operations
2033 // =============================================================================
2034
2035 proptest! {
2036 /// Property: clear_shrink resets to empty state
2037 #[cfg_attr(miri, ignore)]
2038 #[test]
2039 fn prop_clear_resets_state(
2040 values in prop::collection::vec(any::<u32>(), 1..30)
2041 ) {
2042 let mut arena = SlotArena::new();
2043
2044 for value in values {
2045 arena.insert(value);
2046 }
2047
2048 arena.clear_shrink();
2049
2050 prop_assert!(arena.is_empty());
2051 prop_assert_eq!(arena.len(), 0);
2052 prop_assert_eq!(arena.iter().count(), 0);
2053 }
2054
2055 /// Property: clear invalidates all SlotIds
2056 #[cfg_attr(miri, ignore)]
2057 #[test]
2058 fn prop_clear_invalidates_ids(
2059 values in prop::collection::vec(any::<u32>(), 1..20)
2060 ) {
2061 let mut arena = SlotArena::new();
2062 let mut ids = Vec::new();
2063
2064 for value in values {
2065 let id = arena.insert(value);
2066 ids.push(id);
2067 }
2068
2069 arena.clear_shrink();
2070
2071 // All previous ids should be invalid
2072 for id in ids {
2073 prop_assert!(!arena.contains(id));
2074 prop_assert_eq!(arena.get(id), None);
2075 }
2076 }
2077
2078 /// Property: usable after clear
2079 #[cfg_attr(miri, ignore)]
2080 #[test]
2081 fn prop_usable_after_clear(
2082 values1 in prop::collection::vec(any::<u32>(), 1..20),
2083 values2 in prop::collection::vec(any::<u32>(), 1..20)
2084 ) {
2085 let mut arena = SlotArena::new();
2086
2087 for value in values1 {
2088 arena.insert(value);
2089 }
2090
2091 arena.clear_shrink();
2092
2093 // Should be usable after clear
2094 for value in &values2 {
2095 arena.insert(*value);
2096 }
2097
2098 prop_assert_eq!(arena.len(), values2.len());
2099 }
2100 }
2101
2102 // =============================================================================
2103 // Property Tests - Capacity
2104 // =============================================================================
2105
2106 proptest! {
2107 /// Property: with_capacity pre-allocates
2108 #[cfg_attr(miri, ignore)]
2109 #[test]
2110 fn prop_with_capacity_preallocates(
2111 capacity in 1usize..100
2112 ) {
2113 let arena: SlotArena<u32> = SlotArena::with_capacity(capacity);
2114
2115 prop_assert!(arena.capacity() >= capacity);
2116 prop_assert_eq!(arena.len(), 0);
2117 prop_assert!(arena.is_empty());
2118 }
2119 }
2120
2121 // =============================================================================
2122 // Property Tests - Reference Implementation Equivalence
2123 // =============================================================================
2124
2125 proptest! {
2126 /// Property: Behavior matches reference HashMap
2127 #[cfg_attr(miri, ignore)]
2128 #[test]
2129 fn prop_matches_hashmap(
2130 operations in prop::collection::vec((0u8..3, any::<u32>()), 0..50)
2131 ) {
2132 let mut arena = SlotArena::new();
2133 let mut reference: std::collections::HashMap<usize, u32> = std::collections::HashMap::new();
2134
2135 for (op, value) in operations {
2136 match op % 3 {
2137 0 => {
2138 // insert
2139 let id = arena.insert(value);
2140 reference.insert(id.index(), value);
2141 }
2142 1 => {
2143 // remove
2144 if !reference.is_empty() {
2145 let keys: Vec<_> = reference.keys().copied().collect();
2146 let key = keys[0];
2147 let id = SlotId(key);
2148
2149 let arena_val = arena.remove(id);
2150 let ref_val = reference.remove(&key);
2151
2152 prop_assert_eq!(arena_val, ref_val);
2153 }
2154 }
2155 2 => {
2156 // verify
2157 for (&key, &expected_value) in &reference {
2158 let id = SlotId(key);
2159 prop_assert_eq!(arena.get(id), Some(&expected_value));
2160 }
2161 }
2162 _ => unreachable!(),
2163 }
2164
2165 // Verify consistency
2166 prop_assert_eq!(arena.len(), reference.len());
2167 prop_assert_eq!(arena.is_empty(), reference.is_empty());
2168 }
2169 }
2170 }
2171}