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:
- Allocate-then-write: the bytes inside the returned slice are
uninitialised. The caller must write a valid
T(e.g. viacore::ptr::write) before reading. - Drop-before-deallocate:
deallocateoverwrites the slot with a aFreeLink. IfT: Drop, the caller must runT’s destructor (typicallycore::ptr::drop_in_place::<T>(ptr.as_ptr().cast())) before callingdeallocate. Failure to do so leaks resources owned byT(file handles, heap allocations, locks) and may cause UB ifT’s drop is required for soundness. - Layout-must-fit-stride: callers should request layouts whose
size <= block_stride()andalign <= 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 indeallocate. - Slab-drop-does-not-drop-Ts: when the
Slabitself drops, it returns the underlying backing region toBbut does not iterate live slots to dropT. Callers responsible for any still-liveTmust drain them before dropping the slab — e.g. via a higher-level typed wrapper such asGenerationalSlab, which tracks per-slot generations and runsT::dropfor 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>() == 0 → AllocError) 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>
impl<T, B: Allocator + FixedRange> Slab<T, B, NoProtection>
Sourcepub fn new(capacity: usize, backing: B) -> Result<Self, AllocError>
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>
impl<T, B: Allocator + FixedRange, M: FreelistProtection> Slab<T, B, M>
Sourcepub fn with_protection(
capacity: usize,
backing: B,
mac: M,
) -> Result<Self, AllocError>
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.
Sourcepub fn block_stride(&self) -> usize
pub fn block_stride(&self) -> usize
Bytes per slot (≥ size_of::<T>()).
Trait Implementations§
Source§impl<T, B: Allocator + FixedRange, M: FreelistProtection> Allocator for Slab<T, B, M>
impl<T, B: Allocator + FixedRange, M: FreelistProtection> Allocator for Slab<T, B, M>
Source§fn allocate(&self, layout: NonZeroLayout) -> Result<NonNull<[u8]>, AllocError>
fn allocate(&self, layout: NonZeroLayout) -> Result<NonNull<[u8]>, AllocError>
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>
unsafe fn usable_size( &self, ptr: NonNull<u8>, _layout: NonZeroLayout, ) -> Option<usize>
None — implementors that track usable size
override. Read moreSource§fn capacity_bytes(&self) -> Option<usize>
fn capacity_bytes(&self) -> Option<usize>
None for unbounded
allocators like System. Used by Watermark to compute thresholds.Source§fn corruption_events(&self) -> u64
fn corruption_events(&self) -> u64
Source§fn allocate_zeroed(
&self,
layout: NonZeroLayout,
) -> Result<NonNull<[u8]>, AllocError>
fn allocate_zeroed( &self, layout: NonZeroLayout, ) -> Result<NonNull<[u8]>, AllocError>
Source§unsafe fn grow(
&self,
ptr: NonNull<u8>,
old: NonZeroLayout,
new: NonZeroLayout,
) -> Result<NonNull<[u8]>, AllocError>
unsafe fn grow( &self, ptr: NonNull<u8>, old: NonZeroLayout, new: NonZeroLayout, ) -> Result<NonNull<[u8]>, AllocError>
Source§unsafe fn shrink(
&self,
ptr: NonNull<u8>,
old: NonZeroLayout,
new: NonZeroLayout,
) -> Result<NonNull<[u8]>, AllocError>
unsafe fn shrink( &self, ptr: NonNull<u8>, old: NonZeroLayout, new: NonZeroLayout, ) -> Result<NonNull<[u8]>, AllocError>
Source§impl<T, B: Allocator + FixedRange, M: FreelistProtection> Deallocator for Slab<T, B, M>
impl<T, B: Allocator + FixedRange, M: FreelistProtection> Deallocator for Slab<T, B, M>
Source§unsafe fn deallocate(&self, ptr: NonNull<u8>, _layout: NonZeroLayout)
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:
ptrmust 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. viacore::ptr::drop_in_place) before callingdeallocate. This method overwrites the slot’s bytes with aFreeLink. - Passing the same
ptrtwice without an interveningallocateis a double-free and is UB. No protection level — includingSipHashMAC— detects a base-of-slot double-free. The tripwire (next_idx <= capacity) does not catch it: the second free rewrites the slot’s ownFreeLinkto point at the still-live head (an in-range index), so later allocations alias the same live slot.SipHashMACdoes 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. AQuarantinelayer 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.