Skip to main content

WithFallback

Struct WithFallback 

Source
pub struct WithFallback<P, S> { /* private fields */ }
Expand description

Router with primary + secondary allocator.

Primary must implement FixedRange so that deallocation can be routed correctly. Growing primaries (e.g. an ExtendableSlab) cannot be used here — split them out at the application level instead.

§Safety / contract

  • If secondary.contains(ptr) would also return true for a primary-issued pointer (i.e. their address ranges overlap), deallocation routing is incorrect and behavior is UB. In practice the secondary is usually crate::backing::System which doesn’t implement FixedRange, so this concern is hypothetical.
  • Calling deallocate with a pointer that belongs to neither allocator is UB in release builds; debug builds gain a tracking check only when Statistics is composed.

§Watermark composition note

capacity_bytes() reports the primary’s capacity only — the secondary is treated as unbounded overflow drainage, not as part of the budget. Watermark<WithFallback<P, S>> therefore monitors pressure on the FAST path; secondary-side activity does not move the watermark thresholds. This is intentional (you want to know when the bump arena is hot, not when the System heap took a small extra request), but callers who want total-bytes monitoring should wrap each half separately, e.g. WithFallback<Watermark<P, _>, Watermark<S, _>>.

§API-misuse compile-failures (pinned)

The Allocator and Deallocator impls require P: FixedRange so deallocate can route by pointer-provenance. Using a non-FixedRange primary (e.g. crate::backing::System) compiles fine at the new() constructor — WithFallback itself imposes no bound — but the resulting value cannot satisfy the trait bound on allocate:

// FAILS TO COMPILE: `System` is not `FixedRange`, so
// `WithFallback<System, InlineBacked<256>>` does not implement
// `Allocator` and `.allocate(...)` is not a method on it.
use forge_alloc::{InlineBacked, System};
use forge_alloc::{Allocator, NonZeroLayout};
use forge_alloc::WithFallback;
let wf = WithFallback::new(System, InlineBacked::<256>::new());
let layout = NonZeroLayout::from_size_align(64, 8).unwrap();
let _ = wf.allocate(layout);

Implementations§

Source§

impl<P, S> WithFallback<P, S>

Source

pub const fn new(primary: P, secondary: S) -> Self

Construct from existing primary and secondary instances.

This is the const-fn path; no runtime range check is performed. Use this when the secondary doesn’t implement FixedRange (the common case — the canonical secondary is crate::backing::System, which is not FixedRange). For that wiring, deallocation routing is unambiguous: any pointer outside the primary’s range goes to the secondary, and System accepts any pointer.

When BOTH halves implement FixedRange, prefer try_new instead — try_new verifies the two address ranges are disjoint at construction. Overlapping ranges with this constructor will silently misroute secondary-issued pointers through the primary’s deallocate, producing a freelist corruption that is hard to diagnose after the fact.

Source

pub fn primary(&self) -> &P

Borrow the primary allocator.

Source

pub fn secondary(&self) -> &S

Borrow the secondary allocator.

Source

pub fn into_parts(self) -> (P, S)

Decompose into the two halves.

Source§

impl<P: FixedRange, S: FixedRange> WithFallback<P, S>

Source

pub fn try_new(primary: P, secondary: S) -> Result<Self, AllocError>

Construct with a runtime check that the primary and secondary address ranges are disjoint. Returns Err(AllocError) on overlap — overlapping ranges silently misroute deallocations through deallocate’s primary-first contains test, producing a freelist corruption that’s hard to debug after the fact.

Use this constructor whenever both halves implement FixedRange. The default secondary crate::backing::System does not implement FixedRange, so callers wiring System as the fallback continue to use WithFallback::newSystem accepts any pointer, so routing is unambiguous there.

See WithFallback::ranges_disjoint for the exact check.

Source

pub fn ranges_disjoint(&self) -> bool

Return true if the primary and secondary address ranges are disjoint, i.e. no pointer can belong to both.

This is the runtime check behind the type’s safety contract: if the ranges overlap, deallocate’s primary-first routing test can misroute a secondary-issued pointer to primary.deallocate, which then translates it to a slot index via slot_index and pushes a fabricated index onto the freelist — silent corruption.

Call once at construction in a debug_assert!:

let wf = WithFallback::new(primary, secondary);
debug_assert!(wf.ranges_disjoint(), "primary and secondary ranges overlap");

The default secondary crate::backing::System does not implement FixedRange, so this method is unavailable for that common case; System accepts any pointer and routing is unambiguous there.

Trait Implementations§

Source§

impl<P, S> Allocator for WithFallback<P, S>

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§

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

Allocate a zero-initialized block.
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§

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<P, S> Deallocator for WithFallback<P, S>

Source§

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

Release a previously allocated block. Read more
Source§

impl<P: Debug, S: Debug> Debug for WithFallback<P, S>

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

§

impl<P, S> Freeze for WithFallback<P, S>
where P: Freeze, S: Freeze,

§

impl<P, S> RefUnwindSafe for WithFallback<P, S>

§

impl<P, S> Send for WithFallback<P, S>
where P: Send, S: Send,

§

impl<P, S> Sync for WithFallback<P, S>
where P: Sync, S: Sync,

§

impl<P, S> Unpin for WithFallback<P, S>
where P: Unpin, S: Unpin,

§

impl<P, S> UnsafeUnpin for WithFallback<P, S>
where P: UnsafeUnpin, S: UnsafeUnpin,

§

impl<P, S> UnwindSafe for WithFallback<P, S>
where P: UnwindSafe, S: 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.