Skip to content

Release 0.2.0 (Rust 0.2.0, breaking; C 0.1.10)

Choose a tag to compare

@williamwutq williamwutq released this 16 May 04:37
· 643 commits to master since this release

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 0 on every reopen and never changes the on-disk format.
  • Reads (get, get_into, peek_into) that fall entirely within the locked region bypass the internal RwLock and serve directly via pread(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_to pay 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 only

C — 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