tpt-lexregion

Rust

Compiler-assisted lexical memory regions for Rust — O(1) bulk deallocation and zero-fragmentation bump-pointer pools scoped to lexical blocks, for deterministic allocation in HFT, real-time audio, and embedded systems.

0 stars0 forks0 watchers

Languages

Rust99.0%Liquid1.0%
README

tpt-lexregion

CI Crates.io Docs.rs License: MIT OR Apache-2.0

Compiler-assisted lexical memory regions & zero-jitter allocation for Rust.

Deterministic memory management for HFT, real-time audio, game engines, and embedded systems. Open a lexical region, allocate into it through standard collections (Vec, HashMap, Box, …) via the nightly Allocator API, and get the whole pool back in O(1) when the block ends:

  • No free-lists. No fragmentation. One contiguous slab per region; individual deallocations are no-ops by design.
  • Zero allocator jitter at steady state. Heap slabs are recycled through a thread-local cache (std builds), so a loop that opens a region per frame stops touching the global allocator after warm-up.
  • Escape-proof by construction. The region's brand lifetime 'r is an invariant, higher-ranked parameter: rustc itself rejects any attempt to smuggle references, handles, or whole collections out of the block.
  • Multi-threaded variant. SharedRegion uses a lock-free CAS bump cursor; its handles are Send + Sync.
  • no_std friendly. Caller-supplied-buffer entry points work without any heap at all.

License: dual MIT OR Apache-2.0 — © TPT Solutions

Status

CrateVersionNotes
tpt-lexregion0.1.0core crate (public API)
tpt-lexregion-macros0.1.0region! macro + best-effort #[region_attr] lint
tpt-lexregion-benchinternalcriterion suites, not published

Requires nightly Rust (allocator_api, plus btreemap_alloc / closure_lifetime_binder in dependent crates where used). Pinned via rust-toolchain.toml.

The spec's example, working today

#![feature(allocator_api)]
use std::collections::HashMap;
use tpt_lexregion::prelude::*;

fn process_massive_dataset() -> u64 {
    // Open a lexical region; everything allocated inside is bound to it.
    region!(capacity: 64 * 1024 * 1024, {
        // Standard collections allocate directly into the region.
        let mut map: HashMap<u64, Vec<u8>, std::collections::hash_map::RandomState, _> =
            HashMap::new_in(region.allocator());

        for i in 0..100_000u64 {
            map.insert(i, vec![0u8; 64]); // zero global-allocator jitter
        }

        map.values().map(|v| v[0] as u64).sum::<u64>()
        // <-- Region drops here: the pool is freed in O(1). Zero fragmentation.
    })
}

(Deviations from the design doc are recorded in docs/adr-001-escape-analysis-spike.md: the macro takes the body as a trailing block argument, because region!(...) { .. } is not valid macro-invocation grammar.)

Entry points

Function / macroPool backingThreads
with_region / region!heap slab (thread-cached under std)single
with_region_in_buffercaller-supplied buffersingle
with_shared_regionheap slab, lock-free cursormany (Send + Sync)
with_shared_region_in_buffercaller-supplied buffermany
with_growable_regionchained slabs, grows on rollover (capped)single

Domain-shaped runnable examples live in tpt-lexregion/examples/: hft_tick_processing.rs, audio_callback_arena.rs, game_frame_allocator.rs, embedded_sensor_ring.rs (plus the basic/collections/threads/embedded introductions).

Comparison with other arena crates

Verified against current docs.rs pages (Aug 2026). A note on names first: the crates.io crate literally named region is an OS virtual-memory API (mprotect/mlock/VirtualQuery wrappers) — it is not an arena allocator and is unrelated despite the name collision.

tpt-lexregionbumpalotyped-arenagenerativity / ghost-cell
What it isLexical scope-owned regions + nightly Allocator handlesGeneral-purpose growable bump arenaSingle-type arenaBranding / proof-token toolkits (no allocation)
Escape-safety mechanismInvariant brand 'r created by an HRTB closure: rustc rejects every escape, including whole collectionsOrdinary borrows: values can't outlive &Bump, but the arena itself lives wherever you put itSame-lifetime borrows; into_vec can move contents out wholesalemake_guard! invariant brands (fn(&'a()) -> &'a () — the same idiom we build on) / proven-safe GhostToken interior mutability
Pool growthFixed pools fail cleanly (Err(AllocError)); opt-in GrowableRegion chains slabs under a mandatory hard capGrows by chaining fresh chunks automatically; try_* variants availableGrows through its backing storen/a
Failure policyRaw paths never panic; std collections still follow their own OOM conventionGlobal-allocator semantics on growth; fallible variants existGlobal-allocator semanticsn/a
Per-value destructorsOpt-in & explicit: alloc_with_drop / RegionCell (LIFO, panic-safe)Not run in bulk; boxed::Box<T> wrapper drops T individuallyNot run in bulk; into_vec() recovers ownership insteadn/a
ThreadingSharedRegion: lock-free CAS cursor, Send + Sync handles!Sync; bumpalo-herd pools arenas for threadsNot a focusn/a
no_stdYes (buffer-backed APIs need no heap at all)Yes by defaultStd-orientedYes
ToolchainNightly (allocator_api) requiredStable (MSRV 1.71.1); nightly/stable allocator-API adapters optionalStableStable

Honest positioning: if you want a battle-tested general arena on stable Rust, bumpalo is the incumbent and you should probably use it. This crate is for the narrower niche where the lexical scope is the lifetime: the compiler-enforced block boundary (nothing escapes, including collections), fixed-capacity determinism with clean failure paths, thread-local slab reuse for zero steady-state jitter, buffer-backed no_std operation, and opt-in destructors that cannot be forgotten.

Getting started in 5 minutes

Generate a ready-to-build project from the bundled starter template:

cargo install cargo-generate
cargo generate --path template --name my-realtime-app
cd my-realtime-app && cargo run

The generated project pins nightly via its own rust-toolchain.toml, depends on this crate, and contains one runnable region-per-frame example. (Answering "yes" to the use_local_path prompt links a sibling checkout of this repository instead — handy for hacking on the crate itself. CI runs this whole flow as the template-generate job.)

Documentation

  • Safety model — what the compiler enforces vs. what is best-effort.
  • Compiler-errors cookbook — every escape-attempt diagnostic translated into plain English, with fixes.
  • Growable regions — chained slabs, the never-span invariant, and the honest jitter cost of growth.
  • ADR-002: nested regions — why children carve sub-slabs instead of rewinding the cursor, and why shared variants are out of scope.
  • ADR-001: escape-analysis spike — why AST-level escape analysis was rejected as load-bearing, and how the §3.1/§4 macro-form inconsistency was resolved.
  • Concurrency design — lock-free reclamation strategy and Send/Sync bounds; loom model-checking setup.
  • Embedded usage — buffer-backed regions on no_std.
  • Benchmarks & methodology — HFT burst, audio jitter (p50/p99/p99.9/max/stddev), bulk dataset; how to reproduce.

Development

cargo test                      # unit + integration + doctests
cargo test --release -p tpt-lexregion --test datasets   # 1M-insert run
RUSTFLAGS="" cargo clippy --workspace --all-targets -- -D warnings
cargo fmt --all --check
cargo build -p tpt-lexregion --no-default-features \
    --target thumbv7em-none-eabihf                       # embedded check
cargo test -p tpt-lexregion --features loom --release --test loom_model
cargo +nightly miri test -p tpt-lexregion --lib --tests
cargo bench                     # criterion suites (see docs/benchmarks.md)

CI runs all of the above on every push (see .github/workflows/ci.yml).