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 returntruefor a primary-issued pointer (i.e. their address ranges overlap), deallocation routing is incorrect and behavior is UB. In practice the secondary is usuallycrate::backing::Systemwhich doesn’t implementFixedRange, so this concern is hypothetical. - Calling
deallocatewith a pointer that belongs to neither allocator is UB in release builds; debug builds gain a tracking check only whenStatisticsis 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>
impl<P, S> WithFallback<P, S>
Sourcepub const fn new(primary: P, secondary: S) -> Self
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.
Sourcepub fn into_parts(self) -> (P, S)
pub fn into_parts(self) -> (P, S)
Decompose into the two halves.
Source§impl<P: FixedRange, S: FixedRange> WithFallback<P, S>
impl<P: FixedRange, S: FixedRange> WithFallback<P, S>
Sourcepub fn try_new(primary: P, secondary: S) -> Result<Self, AllocError>
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::new — System
accepts any pointer, so routing is unambiguous there.
See WithFallback::ranges_disjoint for the exact check.
Sourcepub fn ranges_disjoint(&self) -> bool
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>
impl<P, S> Allocator for WithFallback<P, S>
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§fn allocate_zeroed(
&self,
layout: NonZeroLayout,
) -> Result<NonNull<[u8]>, AllocError>
fn allocate_zeroed( &self, layout: NonZeroLayout, ) -> Result<NonNull<[u8]>, AllocError>
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.