use std::collections::VecDeque;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum SlabError {
#[error("slab index {0} is out of range (allocator has {1} slabs)")]
SlabIndexOutOfRange(usize, usize),
#[error("slot index {0} is out of range for slab {1} (capacity {2})")]
SlotIndexOutOfRange(usize, usize, usize),
#[error("slot {slot} in slab {slab} is already free")]
SlotAlreadyFree {
slab: usize,
slot: usize,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct SlotHandle {
pub slab: usize,
pub index: usize,
}
enum Slot<T> {
Occupied(T),
Free,
}
pub struct SlabAllocator<T> {
slabs: Vec<Vec<Slot<T>>>,
slab_live: Vec<usize>,
free_list: VecDeque<SlotHandle>,
slab_capacity: usize,
live_count: usize,
total_slots: usize,
}
impl<T> SlabAllocator<T> {
pub fn new(slab_capacity: usize) -> Self {
let cap = slab_capacity.max(1);
Self {
slabs: Vec::new(),
slab_live: Vec::new(),
free_list: VecDeque::new(),
slab_capacity: cap,
live_count: 0,
total_slots: 0,
}
}
pub fn allocate(&mut self, value: T) -> Result<SlotHandle, SlabError> {
let handle = match self.free_list.pop_front() {
Some(h) => h,
None => self.grow_and_get_first_handle(),
};
self.slabs[handle.slab][handle.index] = Slot::Occupied(value);
self.slab_live[handle.slab] += 1;
self.live_count += 1;
Ok(handle)
}
pub fn free(&mut self, handle: SlotHandle) -> Result<(), SlabError> {
self.validate_handle(handle)?;
match &self.slabs[handle.slab][handle.index] {
Slot::Free => Err(SlabError::SlotAlreadyFree {
slab: handle.slab,
slot: handle.index,
}),
Slot::Occupied(_) => {
self.slabs[handle.slab][handle.index] = Slot::Free;
self.slab_live[handle.slab] = self.slab_live[handle.slab].saturating_sub(1);
self.live_count = self.live_count.saturating_sub(1);
self.free_list.push_back(handle);
Ok(())
}
}
}
pub fn get(&self, handle: SlotHandle) -> Result<&T, SlabError> {
self.validate_handle(handle)?;
match &self.slabs[handle.slab][handle.index] {
Slot::Occupied(v) => Ok(v),
Slot::Free => Err(SlabError::SlotAlreadyFree {
slab: handle.slab,
slot: handle.index,
}),
}
}
pub fn get_mut(&mut self, handle: SlotHandle) -> Result<&mut T, SlabError> {
self.validate_handle(handle)?;
match &mut self.slabs[handle.slab][handle.index] {
Slot::Occupied(v) => Ok(v),
Slot::Free => Err(SlabError::SlotAlreadyFree {
slab: handle.slab,
slot: handle.index,
}),
}
}
pub fn compact(&mut self) -> usize {
let before = self.slabs.len();
let keep: Vec<bool> = self.slab_live.iter().map(|&l| l > 0).collect();
let mut new_slabs: Vec<Vec<Slot<T>>> = Vec::new();
let mut new_slab_live: Vec<usize> = Vec::new();
let mut slab_index_map: Vec<Option<usize>> = vec![None; self.slabs.len()];
for (old_idx, should_keep) in keep.iter().enumerate() {
if *should_keep {
let new_idx = new_slabs.len();
slab_index_map[old_idx] = Some(new_idx);
let slab = std::mem::take(&mut self.slabs[old_idx]);
new_slabs.push(slab);
new_slab_live.push(self.slab_live[old_idx]);
}
}
let mut new_free_list: VecDeque<SlotHandle> = VecDeque::new();
for h in self.free_list.drain(..) {
if let Some(Some(new_slab)) = slab_index_map.get(h.slab) {
new_free_list.push_back(SlotHandle {
slab: *new_slab,
index: h.index,
});
}
}
self.slabs = new_slabs;
self.slab_live = new_slab_live;
self.free_list = new_free_list;
self.total_slots = self.slabs.iter().map(|s| s.len()).sum();
before - self.slabs.len()
}
pub fn live_count(&self) -> usize {
self.live_count
}
pub fn total_slots(&self) -> usize {
self.total_slots
}
pub fn slab_count(&self) -> usize {
self.slabs.len()
}
pub fn free_slots(&self) -> usize {
self.free_list.len()
}
pub fn utilisation(&self) -> f64 {
if self.total_slots == 0 {
return 0.0;
}
self.live_count as f64 / self.total_slots as f64
}
fn grow_and_get_first_handle(&mut self) -> SlotHandle {
let slab_idx = self.slabs.len();
let cap = self.slab_capacity;
let mut slab: Vec<Slot<T>> = Vec::with_capacity(cap);
for _ in 0..cap {
slab.push(Slot::Free);
}
self.slabs.push(slab);
self.slab_live.push(0);
self.total_slots += cap;
for i in 1..cap {
self.free_list.push_back(SlotHandle {
slab: slab_idx,
index: i,
});
}
SlotHandle {
slab: slab_idx,
index: 0,
}
}
fn validate_handle(&self, handle: SlotHandle) -> Result<(), SlabError> {
if handle.slab >= self.slabs.len() {
return Err(SlabError::SlabIndexOutOfRange(
handle.slab,
self.slabs.len(),
));
}
let cap = self.slabs[handle.slab].len();
if handle.index >= cap {
return Err(SlabError::SlotIndexOutOfRange(
handle.index,
handle.slab,
cap,
));
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_allocate_and_get() {
let mut alloc: SlabAllocator<u32> = SlabAllocator::new(8);
let h = alloc.allocate(42).expect("allocation should succeed");
assert_eq!(alloc.get(h).expect("get should succeed"), &42);
}
#[test]
fn test_live_count_increments() {
let mut alloc: SlabAllocator<u64> = SlabAllocator::new(4);
assert_eq!(alloc.live_count(), 0);
let _ = alloc.allocate(1).expect("ok");
let _ = alloc.allocate(2).expect("ok");
assert_eq!(alloc.live_count(), 2);
}
#[test]
fn test_free_decrements_live_count() {
let mut alloc: SlabAllocator<u32> = SlabAllocator::new(4);
let h = alloc.allocate(100).expect("ok");
alloc.free(h).expect("free should succeed");
assert_eq!(alloc.live_count(), 0);
}
#[test]
fn test_double_free_returns_error() {
let mut alloc: SlabAllocator<i32> = SlabAllocator::new(4);
let h = alloc.allocate(7).expect("ok");
alloc.free(h).expect("first free ok");
let result = alloc.free(h);
assert!(
matches!(result, Err(SlabError::SlotAlreadyFree { .. })),
"double free should return SlotAlreadyFree"
);
}
#[test]
fn test_slot_reuse() {
let mut alloc: SlabAllocator<u8> = SlabAllocator::new(4);
let h1 = alloc.allocate(1).expect("ok");
alloc.free(h1).expect("free h1");
let h2 = alloc.allocate(2).expect("ok");
assert_eq!(alloc.slab_count(), 1, "no new slab should be needed");
assert_eq!(alloc.get(h2).expect("ok"), &2);
}
#[test]
fn test_multi_slab_allocation() {
let cap = 4usize;
let mut alloc: SlabAllocator<u32> = SlabAllocator::new(cap);
let mut handles = Vec::new();
for i in 0..cap * 3 {
handles.push(alloc.allocate(i as u32).expect("ok"));
}
assert!(alloc.slab_count() >= 3, "at least 3 slabs should exist");
for (i, h) in handles.iter().enumerate() {
assert_eq!(alloc.get(*h).expect("ok"), &(i as u32));
}
}
#[test]
fn test_total_slots() {
let mut alloc: SlabAllocator<()> = SlabAllocator::new(8);
let _ = alloc.allocate(()).expect("ok");
assert_eq!(alloc.total_slots(), 8);
for _ in 0..7 {
let _ = alloc.allocate(()).expect("ok");
}
let _ = alloc.allocate(()).expect("ok"); assert_eq!(alloc.total_slots(), 16);
}
#[test]
fn test_utilisation() {
let mut alloc: SlabAllocator<u8> = SlabAllocator::new(4);
let h = alloc.allocate(0).expect("ok"); let _ = alloc.allocate(0).expect("ok"); alloc.free(h).expect("ok"); let u = alloc.utilisation();
assert!((u - 0.25).abs() < 1e-9, "expected 25% utilisation, got {u}");
}
#[test]
fn test_compact_removes_empty_slabs() {
let mut alloc: SlabAllocator<u32> = SlabAllocator::new(2);
let handles: Vec<_> = (0..5u32).map(|i| alloc.allocate(i).expect("ok")).collect();
assert!(alloc.slab_count() >= 2);
alloc.free(handles[0]).expect("ok");
alloc.free(handles[1]).expect("ok");
let reclaimed = alloc.compact();
assert!(
reclaimed >= 1,
"at least one empty slab should be reclaimed"
);
for _h in &handles[2..] {
}
assert_eq!(
alloc.live_count(),
3,
"3 live objects should survive compact"
);
drop(handles); }
#[test]
fn test_get_mut() {
let mut alloc: SlabAllocator<String> = SlabAllocator::new(4);
let h = alloc.allocate("hello".to_string()).expect("ok");
alloc.get_mut(h).expect("ok").push_str(" world");
assert_eq!(alloc.get(h).expect("ok"), "hello world");
}
#[test]
fn test_invalid_slab_index() {
let alloc: SlabAllocator<u8> = SlabAllocator::new(4);
let bad = SlotHandle { slab: 99, index: 0 };
assert!(matches!(
alloc.get(bad),
Err(SlabError::SlabIndexOutOfRange(99, 0))
));
}
#[test]
fn test_invalid_slot_index() {
let mut alloc: SlabAllocator<u8> = SlabAllocator::new(4);
let _ = alloc.allocate(0).expect("ok");
let bad = SlotHandle { slab: 0, index: 99 };
assert!(matches!(
alloc.get(bad),
Err(SlabError::SlotIndexOutOfRange(99, 0, 4))
));
}
#[test]
fn test_free_slots() {
let mut alloc: SlabAllocator<u8> = SlabAllocator::new(4);
let h = alloc.allocate(1).expect("ok");
assert_eq!(alloc.free_slots(), 3);
alloc.free(h).expect("ok");
assert_eq!(alloc.free_slots(), 4);
}
#[test]
fn test_empty_utilisation() {
let alloc: SlabAllocator<i32> = SlabAllocator::new(8);
assert_eq!(alloc.utilisation(), 0.0);
}
#[test]
fn test_single_slot_slab() {
let mut alloc: SlabAllocator<bool> = SlabAllocator::new(1);
let h1 = alloc.allocate(true).expect("ok");
let h2 = alloc.allocate(false).expect("ok");
assert_ne!(h1, h2);
assert_eq!(alloc.slab_count(), 2);
alloc.free(h1).expect("ok");
alloc.free(h2).expect("ok");
assert_eq!(alloc.live_count(), 0);
}
}