Allocator v0.1.0
Installation
Add Allocator to the [Dependencies] table of your package's Rux.toml:
[Dependencies]
Allocator = { Namespace = "Rux", Version = "0.1.0" }
Standard packages are declared by hand: the registry does not serve them, so rux add Rux/Allocator cannot add them.
Dependencies
Guide
What it provides
| Module | Covers |
|---|---|
Layout | a validated size-and-alignment pair, and the layouts a type or array needs |
Error | AllocError, and the questions worth asking of one |
Contract | the Allocator interface every allocator here implements |
System | SystemAllocator, which asks the operating system for pages |
Box | Box<T>, one heap value with exactly one owner |
Shared | Rc<T> and Arc<T>, one heap value shared by counted handles |
Arena | Arena, which advances a pointer and reclaims everything at once |
FixedBuffer | FixedBuffer, which hands out storage the caller already has |
Pool | Pool, which serves small blocks from per-class free lists |
The module names describe where each declaration comes from; they are not part of an import path.
A Layout that exists is valid. Layout::New refuses an alignment that is not a power of two, or a size whose rounding would pass the end of the address space — so nothing downstream checks again. That second case is the one worth naming: it is how a request for a huge array becomes an allocation far smaller than the caller asked for. Use Layout::ForValue<T>() or Layout::ForArray<T>(count) when storage follows a type; neither requires a sample value, so describing a move-only type consumes nothing.
Four promises hold for every allocator here:
- The address satisfies the layout. Always.
- The contents are undefined. Nothing here zeroes; the page layer does, because the operating system does.
- A release must match its allocation — the same pointer, and an equal layout. The allocator needs the size to know what it is taking back.
- A failure leaves everything alone. A failed reallocation leaves the original block valid and still the caller's.
Every operation returns a native fallible: Allocate and Reallocate give (*var opaque) ! AllocError, and Deallocate gives ! AllocError. Every AllocError case is a failure, so a caller propagates with ?, recovers with catch, and a result left unhandled is diagnosed as discarded. A release on a cleanup path that has nobody to report to says so explicitly with allocator.Deallocate(block, layout) catch { else => {} };.
A zero-sized layout is legal and gives a non-null aligned address that nothing may be read from or written to, and which must still be released. That is cheaper than a null case every caller has to branch on, and it keeps an empty collection from being a special shape.
Arena, Pool, FixedBuffer, and Box<T> prohibit copying. The first two and the box use canonical destructors; the fixed buffer owns no storage but still cannot be copied because duplicate bump state could hand out overlapping blocks. Move an owner explicitly, for example let destination <- source. Box::Create returns Box<T> ! AllocError, destroying the value it was given when the allocator refuses. Destroying a box destroys its value before returning the allocation, while TryTake moves the value to the caller and leaves the box empty.
Rc<T> and Arc<T> are the opposite: copying a handle shares its value and adds to a count, and the value is destroyed when the last handle goes. Rc counts with plain arithmetic for one thread, so it is declared #Unsendable() and cannot be a thread's context; Arc counts atomically, so its copies can be made and dropped on any thread. Both hand out the value for reading through Get, and for writing through GetMutable only while a single handle exists. There are no weak handles, so a cycle of handles is never freed.
Canonical construction is SystemAllocator(), Arena(backing, blockSize), Pool(backing, blocksPerChunk), and FixedBuffer(storage, capacity). Layout::New retains its name because it validates and returns Layout?, so Layout::New(size, alignment) ?? fail AllocError::Unsupported turns an unrepresentable layout into a failure.
An Arena hands out storage by advancing a pointer and takes it all back at once, which is the right shape whenever a program builds many small things whose lives end together. Blocks come from a backing allocator and each is twice the size of the last, so a long-lived arena stops going back to the system. Reset rewinds every allocation while keeping the largest block, so a second round of the same work asks the system for nothing. BytesUsed sums what every block has handed out, alignment padding included; the tail a block is left with when a request moves on to a new block is not counted.
Borrow a concrete implementation directly for a short-lived interface view, for example let view: &var Allocator = system. Arena, pool, and fixed-buffer handles are storable adapters for allocators whose mutable state must stay with an owner. A handle stores a raw pointer because references cannot be fields or returned; it must not outlive or survive a move of its owner.
An arena accepts a release rather than refusing one, so it can stand in for any allocator in generic code that releases what it takes. Ordinary releases do nothing; the most recent allocation is rewound, which is what lets a growing collection in an arena avoid wasting every earlier size it passed through.
A FixedBuffer is the arena's shape without the arena's appetite: it hands out storage the caller already has, never asks anyone for more, and reports OutOfMemory when the buffer runs out. That makes it the allocator for the places where allocation must not happen — a fixed working set, a signal-safe path, a target with no allocator underneath at all. It owns nothing and has no destructor, but its copy operation is prohibited because copying its state while it is in use would hand out the same storage twice.
A Pool is for the other shape — many small values whose lives end at different times and in no particular order. A request is rounded up to one of five power-of-two classes, each keeping its own free list, so both allocation and release are a pointer swap with no search and no coalescing. A class that runs dry takes one chunk from a backing allocator and carves it, so the backing allocator sees one request per chunk rather than one per block. Rounding up is what pays for the speed: a 17-byte request occupies a 32-byte block, and those 15 bytes are not recoverable until the chunk is released. A request too large for the largest class, or needing a stricter alignment than a chunk is taken at, goes straight to the backing allocator — decided from the layout alone, so a release reaches the same conclusion as its allocation without the pool recording anything per block.
SystemAllocator is page-granular, so a small allocation costs a whole page. It is the backing store for an arena or a pool rather than the allocator a program uses for many small values directly.
Index
Interfaces
| Name | Summary |
|---|---|
Allocator | The operations every allocator provides. |
Types
| Name | Summary |
|---|---|
ArenaBlock | One block of arena storage, with the bookkeeping for what has been handed out of it. |
Arena | An allocator that advances a pointer and reclaims everything at once. |
ArenaHandle | A copyable interface adapter pointing at an arena. |
Box<T> | One heap-allocated value of T, owned by exactly one holder. |
AllocError | Why an allocation, reallocation or release did not do what was asked. |
FixedBuffer | An allocator that advances a pointer through storage it does not own. |
FixedBufferHandle | A copyable interface adapter pointing at a FixedBuffer. |
Layout | A validated allocation request: a size, and the alignment its address must satisfy. |
PoolChunk | One chunk of pool storage, carved into blocks of a single class. |
Pool | An allocator that serves small blocks from per-class free lists. |
PoolHandle | A copyable interface adapter pointing at a pool. |
Rc<T> | A shared, counted handle to one value, for use on one thread. |
Arc<T> | A shared, counted handle to one value whose copies may live on different threads. |
SystemBlockHeader | What a system allocation records about itself, immediately before the address the caller sees. |
SystemAllocator | An allocator that asks the operating system for pages. |
Functions
| Name | Summary |
|---|---|
EmptyBlockAddress | The address a zero-sized allocation reports, as a pointer. |
IsEmptyBlock | Whether block is the address a zero-sized allocation reports. |
NaturalAlignment | The alignment a value of size bytes naturally wants. |
ClassIndexFor | The class a layout belongs to, or PoolClassCount for a layout no class can serve. |
Constants
| Name | Summary |
|---|---|
DefaultArenaBlockSize | The default payload of an arena's first block. |
ArenaBlockAlignment | The alignment every arena block is taken at, and so the strictest alignment an arena can serve from one. |
EmptyBlock | The address a zero-sized allocation reports. |
PoolClassCount | How many size classes a pool serves. |
PoolSmallestClass | The smallest size class, and so the block a one-byte request occupies. |
PoolLargestClass | The largest size class. |
PoolAlignment | The alignment every chunk is taken at, and so the strictest alignment a pooled block can satisfy. |
DefaultPoolBlocksPerChunk | How many blocks a chunk holds when the caller does not say. |
SystemBlockMagic | The pattern a valid system block header carries. |