use core::cell::Cell;
use core::fmt;
use core::mem;
use core::ptr;
use alloc::vec::Vec;
use crate::shared::{Full, Slot, SlotCell};
pub struct Claim<'a, T> {
slot_ptr: *mut SlotCell<T>,
slab: &'a Slab<T>,
}
impl<T> Claim<'_, T> {
#[inline]
pub fn write(self, value: T) -> Slot<T> {
let slot_ptr = self.slot_ptr;
unsafe {
(*slot_ptr).write_value(value);
}
mem::forget(self);
unsafe { Slot::from_ptr(slot_ptr) }
}
#[inline]
pub(crate) fn into_ptr(self) -> *mut SlotCell<T> {
let ptr = self.slot_ptr;
mem::forget(self);
ptr
}
}
impl<T> fmt::Debug for Claim<'_, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Claim")
.field("slot_ptr", &self.slot_ptr)
.finish()
}
}
impl<T> Drop for Claim<'_, T> {
fn drop(&mut self) {
let free_head = self.slab.free_head.get();
unsafe {
(*self.slot_ptr).set_next_free(free_head);
}
self.slab.free_head.set(self.slot_ptr);
}
}
pub struct Slab<T> {
slots: core::cell::UnsafeCell<Vec<SlotCell<T>>>,
capacity: usize,
pub(crate) free_head: Cell<*mut SlotCell<T>>,
}
impl<T> Slab<T> {
#[inline]
pub unsafe fn with_capacity(capacity: usize) -> Self {
assert!(capacity > 0, "capacity must be non-zero");
let mut slots = Vec::with_capacity(capacity);
for _ in 0..capacity {
slots.push(SlotCell::vacant(ptr::null_mut()));
}
let slots = core::cell::UnsafeCell::new(slots);
let base = unsafe { (*slots.get()).as_mut_ptr() };
for i in 0..(capacity - 1) {
let next_ptr = base.wrapping_add(i + 1);
unsafe { (*base.add(i)).set_next_free(next_ptr) };
}
Self {
slots,
capacity,
free_head: Cell::new(base),
}
}
#[inline]
pub fn capacity(&self) -> usize {
self.capacity
}
#[inline]
pub(crate) fn slots_ptr(&self) -> *mut SlotCell<T> {
unsafe { (*self.slots.get()).as_mut_ptr() }
}
#[doc(hidden)]
#[inline]
pub fn contains_ptr(&self, ptr: *const ()) -> bool {
let base = self.slots_ptr() as usize;
let end = base + self.capacity * core::mem::size_of::<SlotCell<T>>();
let addr = ptr as usize;
addr >= base && addr < end
}
#[inline]
pub fn claim(&self) -> Option<Claim<'_, T>> {
self.claim_ptr().map(|slot_ptr| Claim {
slot_ptr,
slab: self,
})
}
#[doc(hidden)]
#[inline]
pub(crate) fn claim_ptr(&self) -> Option<*mut SlotCell<T>> {
let slot_ptr = self.free_head.get();
if slot_ptr.is_null() {
return None;
}
let next_free = unsafe { (*slot_ptr).get_next_free() };
self.free_head.set(next_free);
Some(slot_ptr)
}
#[inline]
pub fn alloc(&self, value: T) -> Slot<T> {
self.claim().expect("slab full").write(value)
}
#[inline]
pub fn try_alloc(&self, value: T) -> Result<Slot<T>, Full<T>> {
match self.claim() {
Some(claim) => Ok(claim.write(value)),
None => Err(Full(value)),
}
}
#[inline]
#[allow(clippy::needless_pass_by_value)]
pub fn free(&self, slot: Slot<T>) {
let slot_ptr = slot.into_raw();
debug_assert!(
self.contains_ptr(slot_ptr as *const ()),
"slot was not allocated from this slab"
);
unsafe {
(*slot_ptr).drop_value_in_place();
self.free_ptr(slot_ptr);
}
}
#[inline]
#[allow(clippy::needless_pass_by_value)]
pub fn take(&self, slot: Slot<T>) -> T {
let slot_ptr = slot.into_raw();
debug_assert!(
self.contains_ptr(slot_ptr as *const ()),
"slot was not allocated from this slab"
);
unsafe {
let value = (*slot_ptr).read_value();
self.free_ptr(slot_ptr);
value
}
}
#[doc(hidden)]
#[inline]
pub(crate) unsafe fn free_ptr(&self, slot_ptr: *mut SlotCell<T>) {
debug_assert!(
self.contains_ptr(slot_ptr as *const ()),
"slot was not allocated from this slab"
);
let free_head = self.free_head.get();
unsafe {
(*slot_ptr).set_next_free(free_head);
}
self.free_head.set(slot_ptr);
}
}
impl<T> fmt::Debug for Slab<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Slab")
.field("capacity", &self.capacity)
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::borrow::{Borrow, BorrowMut};
#[test]
fn slab_basic() {
let slab = unsafe { Slab::<u64>::with_capacity(100) };
assert_eq!(slab.capacity(), 100);
let slot = slab.alloc(42);
assert_eq!(*slot, 42);
slab.free(slot);
}
#[test]
fn slab_full() {
let slab = unsafe { Slab::<u64>::with_capacity(2) };
let s1 = slab.alloc(1);
let s2 = slab.alloc(2);
let result = slab.try_alloc(3);
assert!(result.is_err());
let recovered = result.unwrap_err().into_inner();
assert_eq!(recovered, 3);
slab.free(s1);
slab.free(s2);
}
#[test]
fn slot_deref_mut() {
let slab = unsafe { Slab::<String>::with_capacity(10) };
let mut slot = slab.alloc("hello".to_string());
slot.push_str(" world");
assert_eq!(&*slot, "hello world");
slab.free(slot);
}
#[test]
fn slot_dealloc_take() {
let slab = unsafe { Slab::<String>::with_capacity(10) };
let slot = slab.alloc("hello".to_string());
let value = slab.take(slot);
assert_eq!(value, "hello");
}
#[test]
fn slot_size() {
assert_eq!(std::mem::size_of::<Slot<u64>>(), 8);
}
#[test]
fn slab_debug() {
let slab = unsafe { Slab::<u64>::with_capacity(10) };
let s = slab.alloc(42);
let debug = format!("{:?}", slab);
assert!(debug.contains("Slab"));
assert!(debug.contains("capacity"));
slab.free(s);
}
#[test]
fn borrow_traits() {
let slab = unsafe { Slab::<u64>::with_capacity(10) };
let mut slot = slab.alloc(42);
let borrowed: &u64 = slot.borrow();
assert_eq!(*borrowed, 42);
let borrowed_mut: &mut u64 = slot.borrow_mut();
*borrowed_mut = 100;
assert_eq!(*slot, 100);
slab.free(slot);
}
#[cfg(debug_assertions)]
#[test]
fn slot_debug_drop_panics() {
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
let slab = unsafe { Slab::<u64>::with_capacity(10) };
let _slot = slab.alloc(42u64);
}));
assert!(result.is_err(), "Slot should panic on drop in debug mode");
}
#[test]
fn capacity_one() {
let slab = unsafe { Slab::<u64>::with_capacity(1) };
assert_eq!(slab.capacity(), 1);
let slot = slab.alloc(42);
assert!(slab.try_alloc(100).is_err());
slab.free(slot);
let slot2 = slab.alloc(100);
assert_eq!(*slot2, 100);
slab.free(slot2);
}
}