Repository navigation
Release 0.2.0 (Rust 0.2.0, breaking; C 0.1.10)
bstack 0.2.0 (Rust) [Breaking]
This release refines the alloc-feature allocator API with two breaking trait changes and a deprecation, introduces immutable region locking on open files, and adds a zero-overhead manual-coordinate allocator and a convenience supertrait that collapses the breaking changes into a single migration step. The C library advances to 0.1.10 (no ABI change; magic number updated).
Migration Guide (Rust)
Three independent breaking changes affect code that either calls BStackSlice::from_bytes or provides a custom impl BStackAllocator. The quickest path to a clean compile is to apply step 3 first — it satisfies steps 1 and 2 automatically for built-in allocators and shortens the diff for custom ones.
Step 1 — Wrap BStackSlice::from_bytes call sites in unsafe
from_bytes is now unsafe fn. Every existing call site must be wrapped:
// Before
let slice = BStackSlice::from_bytes(allocator, bytes);
// After
let slice = unsafe { BStackSlice::from_bytes(allocator, bytes) };Ensure the encoded offset and length lie within the bounds of the allocator's payload at each call site.
Step 2 — Add associated types to custom impl BStackAllocator blocks
The trait now requires two associated types. For the common case where no custom handle or error type is needed, add these two lines to every impl BStackAllocator for MyType block:
type Error = io::Error;
type Allocated<'a> = BStackSlice<'a, Self>;alloc, realloc, and dealloc now return Result<Self::Allocated<'_>, Self::Error> instead of io::Result<BStackSlice<'_>>. The return type is identical when both associated types are set as above, so no further changes to method bodies are required.
Step 3 — (Optional, recommended) Replace BStackAllocator bounds with BStackSliceAllocator
The new BStackSliceAllocator supertrait bundles BStackAllocator<Error = io::Error> and for<'a> BStackAllocator<Allocated<'a> = BStackSlice<'a, Self>> into one bound. Replacing A: BStackAllocator with A: BStackSliceAllocator in generic bounds is sufficient to satisfy all of the breaking changes above in one step:
// Before
fn store<A: BStackAllocator>(allocator: &A) { … }
// After
fn store<A: BStackSliceAllocator>(allocator: &A) { … }Step 4 — Replace deprecated BStackSlice::new
BStackSlice::new is deprecated in favour of the explicitly-unsafe BStackSlice::from_raw_parts. The body is identical; only the name and unsafe marker change:
// Before
let slice = BStackSlice::new(allocator, offset, len);
// After
let slice = unsafe { BStackSlice::from_raw_parts(allocator, offset, len) };What's New
BStack::lock_up_to — immutable region locking
lock_up_to(n), locked_len(), and open_locked_up_to(path, n) declare the first n payload bytes permanently immutable for the lifetime of the open file handle.
- The boundary is monotonically growing and kept in memory only — it resets to
0on every reopen and never changes the on-disk format. - Reads (
get,get_into,peek_into) that fall entirely within the locked region bypass the internalRwLockand serve directly viapread(2)/ReadFile + OVERLAPPED, reducing contention on read-heavy locked regions. - Writes and shrink operations that overlap the locked region return
InvalidInput. - Callers that never call
lock_up_topay only an uncontended atomic load and comparison per operation.
ManualAllocator — typed coordinate handle without a backing allocator (alloc)
A singleton allocator with no backing BStack. alloc and realloc always return Err(Unsupported); dealloc is a no-op. Intended for code that wants BStackSlice as a typed (offset, len) coordinate — serialised, stored, compared — while managing the BStack directly. Obtain via ManualAllocator::get().
BStackSliceAllocator convenience supertrait (alloc)
Bundles BStackAllocator<Error = io::Error> and for<'a> BStackAllocator<Allocated<'a> = BStackSlice<'a, Self>> into a single bound covering the common no-custom-error case. All allocators provided by this library implement it automatically via a blanket impl.
BStackSlice::from_raw_parts — explicit-unsafe constructor (alloc)
Explicit-unsafe replacement for the deprecated BStackSlice::new. The safety contract requires that offset + len does not overflow u64, and — if the slice will be passed to realloc or dealloc — that the (offset, len) pair describes an allocation originally returned by alloc or a prior realloc on the same allocator instance.
Breaking Changes
| Symbol | Kind | Change |
|---|---|---|
BStackSlice::from_bytes |
alloc |
Now unsafe fn; callers must add unsafe block |
BStackAllocator::alloc / realloc / dealloc |
alloc |
Return type changed to Result<Self::Allocated<'_>, Self::Error> |
BStackAllocator |
alloc |
Requires type Error and type Allocated<'a> associated types |
BStackBulkAllocator::alloc_bulk |
alloc |
Returns Vec<Self::Allocated<'_>> |
BStackBulkAllocator::dealloc_bulk |
alloc |
Accepts impl AsRef<[Self::Allocated<'a>]> |
Deprecated
| Symbol | Replacement |
|---|---|
BStackSlice::new |
unsafe { BStackSlice::from_raw_parts(allocator, offset, len) } |
Platforms
This release ships pre-built static libraries for eight platforms:
| Archive | Architecture | OS |
|---|---|---|
x86_64-apple-darwin.tar.gz |
x86-64 | macOS |
aarch64-apple-darwin.tar.gz |
Apple Silicon (arm64) | macOS |
x86_64-unknown-linux-gnu.tar.gz |
x86-64 | Linux (glibc) |
aarch64-unknown-linux-gnu.tar.gz |
arm64 | Linux (glibc) |
x86_64-unknown-linux-musl.tar.gz |
x86-64 | Linux (musl) |
aarch64-unknown-linux-musl.tar.gz |
arm64 | Linux (musl) |
x86_64-pc-windows-gnu.zip |
x86-64 | Windows |
aarch64-pc-windows-gnullvm.zip |
arm64 | Windows |
Feature variants
Each archive ships seven pre-built library variants. Choose the one that matches the features you use.
| Variant | Rust | C |
|---|---|---|
| Default | libbstack.rlib |
libbstack.a |
set |
libbstack-set.rlib |
libbstack-set.a |
alloc |
libbstack-alloc.rlib |
libbstack-alloc.a |
alloc + set |
libbstack-alloc-set.rlib |
libbstack-alloc-set.a |
atomic |
libbstack-atomic.rlib |
libbstack-atomic.a |
set + atomic |
libbstack-set-atomic.rlib |
libbstack-set-atomic.a |
guarded |
libbstack-guarded.rlib |
libbstack-guarded.a |
The alloc + set combination is required for FirstFitBStackAllocator / first_fit_bstack_allocator_t. The set + atomic combination is required for swap, cas, and process. The guarded feature can be combined with any other feature. C archives include both bstack.h and bstack_alloc.h.
Rust — add to Cargo.toml:
[dependencies]
bstack = { version = "0.2", features = ["alloc", "set"] } # first-fit or ghost-tree allocator
bstack = { version = "0.2", features = ["set", "atomic"] } # atomic ops
bstack = { version = "0.2", features = ["atomic"] } # atomic ops on the tail only
bstack = { version = "0.2", features = ["set"] } # set without alloc
bstack = { version = "0.2", features = ["guarded"] } # guarded slice abstraction
bstack = { version = "0.2" } # core onlyC — link against the appropriate library and define the corresponding macros:
# alloc + set (first-fit or ghost-tree allocator)
cc -DBSTACK_FEATURE_SET -I path/to/c myapp.c -L path/to/c -lbstack-alloc-set
# set + atomic
cc -DBSTACK_FEATURE_SET -DBSTACK_FEATURE_ATOMIC -I path/to/c myapp.c -L path/to/c -lbstack-set-atomic
# guarded
cc -DBSTACK_FEATURE_GUARDED -I path/to/c myapp.c -L path/to/c -lbstack-guarded
# core only
cc -I path/to/c myapp.c -L path/to/c -lbstack