Skip to main content

plugmem_arena/
lib.rs

1//! Flat byte-pool storage structures for plugmem.
2//!
3//! This crate is the storage foundation of the plugmem engine, but it is
4//! deliberately generic: nothing here knows about facts, vectors or LLMs.
5//! If you need a compact, allocation-frugal, `no_std` sorted container whose
6//! in-memory representation *is* its serialized form, you can lift it into
7//! your own project as-is — see `examples/` for self-contained walkthroughs.
8//!
9//! # Philosophy
10//!
11//! 1. **State is flat bytes.** A container is one contiguous byte pool plus a
12//!    few small metadata arrays. No per-element allocations, no pointers, no
13//!    `Box`/`Rc` graphs. Persisting a container is `memcpy`; loading it back
14//!    is bounds-checking the metadata and adopting the bytes.
15//! 2. **Costs are local and visible.** Every operation touches one 4 KiB
16//!    page (one cache-friendly unit). Worst cases are small, fixed and
17//!    measured — the optional [`counters`](#feature-flags) feature exposes
18//!    deterministic work counters used as CI performance gates.
19//! 3. **Keys are big-endian.** Byte-wise comparison of an encoded key must
20//!    equal the numeric comparison of its source value, so binary search and
21//!    ordered iteration work directly on raw bytes. Helpers live in [`key`].
22//!
23//! # The four structures
24//!
25//! | Structure | Shape | Typical use |
26//! |---|---|---|
27//! | [`Arena`] | sorted fixed-size records, sharded 4 KiB pages | primary record store, ordered indexes |
28//! | [`BlobHeap`] | append-only variable-length blobs, dense ids | texts, names, raw vectors |
29//! | [`ChunkPool`] | many small growable lists over 64-byte chunks | posting lists, adjacency lists |
30//! | [`Interner`] | string -> dense `u32` (heap + flat hash table) | terms, tags, entity names |
31//!
32//! # Quick start
33//!
34//! ```
35//! use plugmem_arena::{Arena, ArenaCfg, ShardMode, Slot, key};
36//!
37//! /// A tiny fixed-size record: 4-byte big-endian key + 1-byte payload.
38//! #[derive(Debug, PartialEq)]
39//! struct Rec {
40//!     id: u32,
41//!     level: u8,
42//! }
43//!
44//! impl Slot for Rec {
45//!     const SIZE: usize = 5;
46//!     const KEY_LEN: usize = 4;
47//!     fn write(&self, out: &mut [u8]) {
48//!         key::write_u32(out, self.id);
49//!         out[4] = self.level;
50//!     }
51//!     fn read(bytes: &[u8]) -> Self {
52//!         Rec { id: key::read_u32(bytes), level: bytes[4] }
53//!     }
54//! }
55//!
56//! let mut arena = Arena::<Rec>::new(ArenaCfg::new(64, ShardMode::Ordered)).unwrap();
57//! arena.insert(&Rec { id: 7, level: 3 }).unwrap();
58//! arena.insert(&Rec { id: 1, level: 9 }).unwrap();
59//!
60//! let mut key_buf = [0u8; 4];
61//! key::write_u32(&mut key_buf, 7);
62//! assert_eq!(arena.get(&key_buf), Some(Rec { id: 7, level: 3 }));
63//!
64//! // Ordered mode: iteration yields ascending keys across all shards.
65//! let ids: Vec<u32> = arena.iter().map(|r| r.id).collect();
66//! assert_eq!(ids, [1, 7]);
67//! ```
68//!
69//! # The one `unsafe`
70//!
71//! The single default `unsafe` in this crate is page allocation without
72//! zeroing (`Vec::reserve` + `set_len`). It is kept because it was
73//! *measured*, not assumed: on the wasm target (our primary portability
74//! target) zeroing freshly grown pages made the allocation path **12x
75//! slower** (wasmtime, 32k pages: 3889 us zeroed vs 316 us uninit), while on
76//! native x86-64 the difference is noise. The safety invariant is simple and
77//! local: *bytes of a page beyond `count * Slot::SIZE` are never read* —
78//! every read is bounded by the per-shard element count, and a slot is fully
79//! written before `count` is incremented. See `Arena::ensure_page` for the
80//! full safety comment. A consequence worth knowing: `Arena` intentionally
81//! implements neither `Clone` nor `PartialEq`, because a byte-wise clone or
82//! comparison would read those uninitialized tails.
83//!
84//! Bounds-check elimination (`get_unchecked`) was measured on the same
85//! harness and rejected: <= 1% on native, *slower* under wasm (the runtime
86//! bounds-checks linear memory anyway). Safe indexing everywhere else.
87//!
88//! # Feature flags
89//!
90//! - `std` *(default)* — nothing yet beyond linking `std` for consumers'
91//!   convenience; the crate is fully functional as `no_std + alloc`.
92//! - `counters` — deterministic work counters (`Counters`) on every
93//!   container: key comparisons, bytes shifted, pages allocated. Zero cost
94//!   when disabled (the increments compile away).
95#![no_std]
96
97extern crate alloc;
98
99pub mod key;
100
101mod arena;
102mod blob;
103mod chunk;
104mod error;
105mod interner;
106mod paged;
107mod slot;
108
109pub use arena::{Arena, ArenaCfg, Iter, PAGE_BYTES, ShardMode};
110pub use blob::{BlobHeap, BlobHeapBuilder, BlobHeapCfg, BlobId};
111pub use chunk::{CHUNK_BYTES, CHUNK_PAYLOAD, ChunkIter, ChunkPool, ChunkPoolCfg, ListHandle};
112pub use error::Error;
113pub use interner::{Interner, TermId};
114pub use slot::Slot;
115
116#[cfg(feature = "counters")]
117pub use arena::Counters;