Skip to main content

Slab

Struct Slab 

Source
pub struct Slab<T, B: Allocator + FixedRange, M: FreelistProtection = NoProtection> { /* private fields */ }
Expand description

Fixed-stride typed slab.

T is the value type; B is the underlying backing (any Allocator); M is the freelist integrity policy (default NoProtection, with zero overhead). The slab takes one large allocation from B at construction; individual allocate / deallocate calls return slots from within that allocation in O(1).

§Usage discipline (read before unsafe-calling either trait method)

Slab issues raw NonNull<u8> pointers into typed-but-uninitialised slots — it does not track value lifecycle. The caller is responsible for the four following invariants:

  1. Allocate-then-write: the bytes inside the returned slice are uninitialised. The caller must write a valid T (e.g. via core::ptr::write) before reading.
  2. Drop-before-deallocate: deallocate overwrites the slot with a a FreeLink. If T: Drop, the caller must run T’s destructor (typically core::ptr::drop_in_place::<T>(ptr.as_ptr().cast())) before calling deallocate. Failure to do so leaks resources owned by T (file handles, heap allocations, locks) and may cause UB if T’s drop is required for soundness.
  3. Layout-must-fit-stride: callers should request layouts whose size <= block_stride() and align <= max(align_of::<T>(), align_of::<FreeLink>()). Mis-sized requests fail at allocate; mis-aligned ones may not match a slot index and trip a debug assertion in deallocate.
  4. Slab-drop-does-not-drop-Ts: when the Slab itself drops, it returns the underlying backing region to B but does not iterate live slots to drop T. Callers responsible for any still-live T must drain them before dropping the slab — e.g. via a higher-level typed wrapper such as GenerationalSlab, which tracks per-slot generations and runs T::drop for outstanding handles.

Slab::allocate returns a NonNull<[u8]> whose slice length is block_stride(), not the requested layout.size(). Callers who care about the exact requested size must remember it themselves; callers who want to use the extra padding bytes (e.g. for footers / metadata) may write through the full stride window.

§Thread safety

Send if T, B, M are Send. Sync: NO. The free list head and next-uncarved cursor live in UnsafeCells so that Allocator::allocate can take &self. Cross-thread deallocation uses SlabRemote — not this type directly.

§API-misuse compile-failures (pinned)

T must not be a zero-sized type. The ASSERT_T_NON_ZST associated const turns the previously runtime-only rejection (size_of::<T>() == 0AllocError) into a build error, so the failure surfaces at the call site instead of after a successful build.

// FAILS TO COMPILE: ZST T is rejected by `Slab::ASSERT_T_NON_ZST`.
// The const_assert fires when `with_protection` is monomorphised, so
// the build halts before any test runs.
use forge_alloc::InlineBacked;
use forge_alloc::Slab;
let _: Slab<(), InlineBacked<128>> =
    Slab::new(8, InlineBacked::<128>::new()).unwrap();

Implementations§

Source§

impl<T, B: Allocator + FixedRange> Slab<T, B, NoProtection>

Source

pub fn new(capacity: usize, backing: B) -> Result<Self, AllocError>

Construct a slab with the default NoProtection policy.

capacity is the number of T slots. Errors if the backing cannot supply the required region or if the total size overflows.

Source§

impl<T, B: Allocator + FixedRange, M: FreelistProtection> Slab<T, B, M>

Source

pub fn with_protection( capacity: usize, backing: B, mac: M, ) -> Result<Self, AllocError>

Construct a slab with an explicit freelist-protection policy.

capacity must be > 0 and ≤ u32::MAX — the slab uses 32-bit slot indices internally. T must not be a ZST (a freelist over zero-sized slots has no meaning); this is now enforced at compile time via ASSERT_T_NON_ZST — instantiating Slab<(), _, _> is a build error rather than a runtime AllocError.

Source

pub fn capacity(&self) -> usize

Number of slots in this slab.

Source

pub fn block_stride(&self) -> usize

Bytes per slot (≥ size_of::<T>()).

Source

pub fn backing(&self) -> &B

Borrow the underlying backing.

Trait Implementations§

Source§

impl<T, B: Allocator + FixedRange, M: FreelistProtection> Allocator for Slab<T, B, M>

Source§

fn allocate(&self, layout: NonZeroLayout) -> Result<NonNull<[u8]>, AllocError>

Allocate a block satisfying layout. The returned slice’s length is at least layout.size() but may be larger.
Source§

unsafe fn usable_size( &self, ptr: NonNull<u8>, _layout: NonZeroLayout, ) -> Option<usize>

Usable size of an existing allocation, if the allocator can report it. Defaults to None — implementors that track usable size override. Read more
Source§

fn capacity_bytes(&self) -> Option<usize>

Total bytes this allocator can issue, if bounded. None for unbounded allocators like System. Used by Watermark to compute thresholds.
Source§

fn corruption_events(&self) -> u64

Detected freelist / metadata corruption events observed by this allocator since construction. Read more
Source§

fn allocate_zeroed( &self, layout: NonZeroLayout, ) -> Result<NonNull<[u8]>, AllocError>

Allocate a zero-initialized block.
Source§

unsafe fn grow( &self, ptr: NonNull<u8>, old: NonZeroLayout, new: NonZeroLayout, ) -> Result<NonNull<[u8]>, AllocError>

Grow an allocation in place if possible, otherwise allocate-copy-free. Read more
Source§

unsafe fn shrink( &self, ptr: NonNull<u8>, old: NonZeroLayout, new: NonZeroLayout, ) -> Result<NonNull<[u8]>, AllocError>

Shrink an allocation in place if possible, otherwise allocate-copy-free. Read more
Source§

fn reset(&mut self) -> Result<(), AllocError>

Reclaim everything previously allocated. Default impl returns AllocError — only arena-style allocators implement a meaningful reset. Read more
Source§

impl<T, B: Allocator + FixedRange, M: FreelistProtection> Deallocator for Slab<T, B, M>

Source§

unsafe fn deallocate(&self, ptr: NonNull<u8>, _layout: NonZeroLayout)

Push the slot identified by ptr onto the freelist.

§Safety

Per the Deallocator contract, ptr must have been returned by a previous call to self.allocate(layout). Specifically:

  • ptr must lie at the base of a slot in this slab (not an offset within a slot, not a pointer from another slab or allocator).
  • The caller is responsible for running T’s destructor (e.g. via core::ptr::drop_in_place) before calling deallocate. This method overwrites the slot’s bytes with a FreeLink.
  • Passing the same ptr twice without an intervening allocate is a double-free and is UB. No protection level — including SipHashMAC — detects a base-of-slot double-free. The tripwire (next_idx <= capacity) does not catch it: the second free rewrites the slot’s own FreeLink to point at the still-live head (an in-range index), so later allocations alias the same live slot. SipHashMAC does not catch it either, because the MAC binds a link to its own slot index: the second free re-signs a perfectly valid MAC for that same index in place, which then verifies on pop. The MAC’s protection is against a link forged or relocated to a different slot (and the move-safety false-fail the index nonce fixes) — not an in-place re-sign. Detecting double-free needs orthogonal per-slot state — a live/free bit or a generation tag, e.g. GenerationalSlab, which rejects a stale handle on its second free — which this slab does not carry by design. A Quarantine layer does not detect it (it keeps no per-slot state and would forward both frees to the inner slab); it only delays slot reuse, shrinking the window in which the aliased slot is handed back out.
Source§

impl<T, B: Allocator + FixedRange, M: FreelistProtection> Drop for Slab<T, B, M>

Source§

fn drop(&mut self)

Executes the destructor for this type. Read more
Source§

fn pin_drop(self: Pin<&mut Self>)

🔬This is a nightly-only experimental API. (pin_ergonomics)
Execute the destructor for this type, but different to Drop::drop, it requires self to be pinned. Read more
Source§

impl<T, B: Allocator + FixedRange, M: FreelistProtection> FixedRange for Slab<T, B, M>

Source§

fn base(&self) -> NonNull<u8>

First byte of the owned address range. Read more
Source§

fn size(&self) -> usize

Length in bytes of the owned address range. Read more
Source§

fn commit(&self, offset: usize, len: usize) -> Result<(), AllocError>

Ensure the bytes [offset, offset + len) (relative to base) are backed by committed, writable memory before a consumer writes through them. Read more
Source§

fn contains(&self, ptr: NonNull<u8>) -> bool

Whether ptr lies within [base, base + size). Read more
Source§

impl<T, B, M> Send for Slab<T, B, M>

Auto Trait Implementations§

§

impl<T, B, M = NoProtection> !Freeze for Slab<T, B, M>

§

impl<T, B, M = NoProtection> !RefUnwindSafe for Slab<T, B, M>

§

impl<T, B, M = NoProtection> !Sync for Slab<T, B, M>

§

impl<T, B, M> Unpin for Slab<T, B, M>
where B: Unpin, M: Unpin, T: Unpin,

§

impl<T, B, M> UnsafeUnpin for Slab<T, B, M>
where B: UnsafeUnpin, M: UnsafeUnpin,

§

impl<T, B, M> UnwindSafe for Slab<T, B, M>
where B: UnwindSafe, M: UnwindSafe, T: UnwindSafe,

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.