Skip to main content

BStack

Struct BStack 

Source
pub struct BStack { /* private fields */ }
Expand description

A persistent, fsync-durable binary stack backed by a single file.

See the crate-level documentation for the file format, durability guarantees, crash recovery, multi-process safety, and thread-safety model.

Implementations§

Source§

impl BStack

Source

pub fn open(path: impl AsRef<Path>) -> Result<Self>

Open or create a stack file at path.

On a new file the 16-byte header is written and durably synced before returning.

On an existing file the header is validated and, if a previous crash left the file in an inconsistent state, the file is repaired and durably synced before returning (see Crash recovery in the crate docs).

On Unix an exclusive advisory flock is acquired; if another process already holds the lock this function returns immediately with io::ErrorKind::WouldBlock.

§Errors
Source

pub fn push(&self, data: impl AsRef<[u8]>) -> Result<u64>

Append data to the end of the file.

Returns the logical byte offset at which data begins — i.e. the payload size immediately before the write. An empty slice is valid; it writes nothing and returns the current end offset.

§Atomicity

Either the full payload is written, the header committed-length is updated, and the whole thing is durably synced, or the file is left unchanged (best-effort rollback via ftruncate + header reset).

§Errors

Returns any io::Error from write_all, durable_sync, or the fallback set_len.

Source

pub fn extend(&self, n: u64) -> Result<u64>

Append n zero bytes to the end of the file.

Returns the logical byte offset at which the zeros begin — i.e. the payload size immediately before the write. n = 0 is valid; it writes nothing and returns the current end offset.

§Atomicity

Either the file is extended, the header committed-length is updated, and the whole thing is durably synced, or the file is left unchanged (best-effort rollback via ftruncate + header reset).

§Errors

Returns any io::Error from set_len, durable_sync, or the fallback set_len.

Source

pub fn pop(&self, n: u64) -> Result<Vec<u8>>

Remove and return the last n bytes of the file.

n = 0 is valid: no bytes are removed and an empty Vec is returned. n may span across multiple previous push boundaries.

§Atomicity

The bytes are read before the file is truncated. The committed-length in the header is updated and durably synced after the truncation.

§Errors

Returns io::ErrorKind::InvalidInput if n exceeds the current payload size. Also propagates any I/O error from read_exact, set_len, write_all, or durable_sync.

Source

pub fn peek(&self, offset: u64) -> Result<Vec<u8>>

Return a copy of every payload byte from offset to the end of the file.

offset is a logical offset (as returned by push). offset == len() is valid and returns an empty Vec. The file is not modified.

§Concurrency

On Unix and Windows this uses a cursor-safe positional read (pread(2) on Unix; ReadFile+OVERLAPPED on Windows), so the method takes only the read lock, allowing multiple concurrent peek and get calls to run in parallel.

On other platforms a seek is required; the method falls back to the write lock and concurrent reads serialise.

§Errors

Returns io::ErrorKind::InvalidInput if offset exceeds the current payload size.

Source

pub fn get(&self, start: u64, end: u64) -> Result<Vec<u8>>

Return a copy of the bytes in the half-open logical range [start, end).

start == end is valid and returns an empty Vec. The file is not modified.

§Concurrency

Same as peek: on Unix and Windows the read lock is taken and concurrent get/peek/len calls may run in parallel. On other platforms the write lock is taken and reads serialise.

§Errors

Returns io::ErrorKind::InvalidInput if end < start or if end exceeds the current payload size.

Source

pub fn peek_into(&self, offset: u64, buf: &mut [u8]) -> Result<()>

Fill buf with bytes from logical offset to offset + buf.len().

Reads exactly buf.len() bytes from offset into the caller-supplied buffer. An empty buffer is a valid no-op. The file is not modified.

Use this instead of peek when the destination buffer is already allocated and you want to avoid the extra heap allocation.

§Concurrency

Same as peek: on Unix and Windows only the read lock is taken; on other platforms the write lock serialises all reads.

§Errors

Returns io::ErrorKind::InvalidInput if offset + buf.len() overflows u64 or exceeds the current payload size.

Source

pub fn get_into(&self, start: u64, buf: &mut [u8]) -> Result<()>

Fill buf with bytes from the half-open logical range [start, start + buf.len()).

An empty buffer is a valid no-op. The file is not modified.

Use this instead of get when the destination buffer is already allocated and you want to avoid the extra heap allocation.

§Concurrency

Same as get: on Unix and Windows only the read lock is taken; on other platforms the write lock serialises all reads.

§Errors

Returns io::ErrorKind::InvalidInput if start + buf.len() overflows u64 or exceeds the current payload size.

Source

pub fn pop_into(&self, buf: &mut [u8]) -> Result<()>

Remove the last buf.len() bytes from the file and write them into buf.

An empty buffer is a valid no-op: no bytes are removed.

Use this instead of pop when the destination buffer is already allocated and you want to avoid the extra heap allocation.

§Atomicity

Same guarantees as pop.

§Errors

Returns io::ErrorKind::InvalidInput if buf.len() exceeds the current payload size. Also propagates any I/O error from read_exact, set_len, write_all, or durable_sync.

Source

pub fn discard(&self, n: u64) -> Result<()>

Remove (discard) the last n bytes from the file without returning them.

Equivalent to pop but avoids allocating a buffer for the removed bytes. n = 0 is valid and is a no-op.

§Atomicity

Same guarantees as pop.

§Errors

Returns io::ErrorKind::InvalidInput if n exceeds the current payload size. Also propagates any I/O error from set_len, write_all, or durable_sync.

Source

pub fn set(&self, offset: u64, data: impl AsRef<[u8]>) -> Result<()>

Overwrite data bytes in place starting at logical offset.

The file size is never changed: if offset + data.len() would exceed the current payload size the call is rejected. An empty slice is a valid no-op.

§Feature flag

Only available when the set Cargo feature is enabled.

§Durability

Equivalent to push/pop: the overwritten bytes are durably synced before the call returns.

§Errors

Returns io::ErrorKind::InvalidInput if offset + data.len() exceeds the current payload size, or if the addition overflows u64. Propagates any I/O error from write_all or durable_sync.

Source

pub fn zero(&self, offset: u64, n: u64) -> Result<()>

Overwrite n bytes with zeros in place starting at logical offset.

The file size is never changed: if offset + n would exceed the current payload size the call is rejected. n = 0 is a valid no-op.

§Feature flag

Only available when the set Cargo feature is enabled.

§Durability

Equivalent to push/pop: the overwritten bytes are durably synced before the call returns.

§Errors

Returns io::ErrorKind::InvalidInput if offset + n exceeds the current payload size, or if the addition overflows u64. Propagates any I/O error from write_all or durable_sync.

Source§

impl BStack

Source

pub fn atrunc(&self, n: u64, buf: impl AsRef<[u8]>) -> Result<()>

Cut n bytes off the tail then append buf as a single atomic operation.

The operation ordering is chosen based on the net size change to maximise crash-recovery safety (see Durability in the crate docs):

  • Net extension (buf.len() > n): the file is extended first, buf is written into the freed tail region plus the new space, then a durable_sync commits the data before the header committed-length is updated. On crash before the header update, recovery truncates back to the original committed length — a clean rollback.

  • Net truncation or same size (buf.len() ≤ n): buf is written into the tail first, then the file is truncated, then durable_sync commits the result before the header is updated. On crash after truncation, recovery sets the committed length to the (smaller) file size, committing the final state.

n = 0 with an empty buf is a valid no-op.

§Feature flag

Only available when the atomic Cargo feature is enabled.

§Errors

Returns io::ErrorKind::InvalidInput if n exceeds the current payload size. Propagates any I/O error from set_len, write_all, or durable_sync.

Source

pub fn splice(&self, n: u64, buf: impl AsRef<[u8]>) -> Result<Vec<u8>>

Pop n bytes off the tail then append buf, returning the removed bytes.

The bytes are read before any mutation, so they are always available in the returned Vec even if the subsequent write fails. The same ordering strategy as atrunc is used.

n = 0 with an empty buf is a valid no-op and returns an empty Vec.

§Feature flag

Only available when the atomic Cargo feature is enabled.

§Errors

Returns io::ErrorKind::InvalidInput if n exceeds the current payload size. Propagates any I/O error from read_exact, set_len, write_all, or durable_sync.

Source

pub fn splice_into(&self, old: &mut [u8], new: impl AsRef<[u8]>) -> Result<()>

Pop old.len() bytes off the tail into old, then append new.

Buffer-reuse counterpart of splice: avoids allocating a Vec for the removed bytes by writing them into the caller-supplied old slice. The same ordering strategy as atrunc is used for the write/truncation side.

An empty old with an empty new is a valid no-op.

§Feature flag

Only available when the atomic Cargo feature is enabled.

§Errors

Returns io::ErrorKind::InvalidInput if old.len() exceeds the current payload size. Propagates any I/O error from read_exact, set_len, write_all, or durable_sync.

Source

pub fn try_extend(&self, s: u64, buf: impl AsRef<[u8]>) -> Result<bool>

Append buf only if the current logical payload size equals s.

Returns Ok(true) if the size matched and buf was appended (or buf is empty and no I/O was needed). Returns Ok(false) without modifying the file if the size does not match.

§Feature flag

Only available when the atomic Cargo feature is enabled.

§Errors

Propagates any I/O error from write_all, write_committed_len, or durable_sync.

Source

pub fn try_discard(&self, s: u64, n: u64) -> Result<bool>

Discard n bytes only if the current logical payload size equals s.

Returns Ok(true) if the size matched and n bytes were removed (or n = 0 and the size check passed without I/O). Returns Ok(false) without modifying the file if the size does not match.

When n = 0 only the read lock is taken (no file mutation occurs).

§Feature flag

Only available when the atomic Cargo feature is enabled.

§Errors

Returns io::ErrorKind::InvalidInput if n exceeds the current payload size. Propagates any I/O error from set_len, write_committed_len, or durable_sync.

Source

pub fn replace<F>(&self, n: u64, f: F) -> Result<()>
where F: FnOnce(&[u8]) -> Vec<u8>,

Pop n bytes off the tail, pass them read-only to a callback that returns the new tail bytes, then write the new tail.

The read, callback invocation, and write all happen under the same write lock, so no other thread can observe the state between the pop and the push. The callback may return a Vec<u8> of any length — the file will grow or shrink accordingly using the same crash-safe ordering strategy as atrunc.

n = 0 is valid: the callback receives an empty slice and whatever it returns is appended.

§Feature flag

Only available when the atomic Cargo feature is enabled.

§Errors

Returns io::ErrorKind::InvalidInput if n exceeds the current payload size. Propagates any I/O error from read_exact, set_len, write_all, or durable_sync.

Source§

impl BStack

Source

pub fn swap(&self, offset: u64, buf: impl AsRef<[u8]>) -> Result<Vec<u8>>

Atomically read buf.len() bytes at offset and overwrite them with buf, returning the old contents.

Both the read and the write happen under the same write lock, so no other thread can observe either the pre-swap or mid-swap state. The file size is never changed.

An empty buf is a valid no-op and returns an empty Vec.

§Feature flags

Only available when both the set and atomic Cargo features are enabled.

§Errors

Returns io::ErrorKind::InvalidInput if offset + buf.len() overflows u64 or exceeds the current payload size. Propagates any I/O error from read_exact, write_all, or durable_sync.

Source

pub fn swap_into(&self, offset: u64, buf: &mut [u8]) -> Result<()>

Atomically read buf.len() bytes at offset into buf while writing the original contents of buf into that position.

On return, buf contains the bytes that were previously at offset, and the file contains what buf held on entry. Buffer-reuse counterpart of swap.

An empty buf is a valid no-op.

§Feature flags

Only available when both the set and atomic Cargo features are enabled.

§Errors

Returns io::ErrorKind::InvalidInput if offset + buf.len() overflows u64 or exceeds the current payload size. Propagates any I/O error from read_exact, write_all, or durable_sync.

Source

pub fn cas( &self, offset: u64, old: impl AsRef<[u8]>, new: impl AsRef<[u8]>, ) -> Result<bool>

Compare-and-exchange: read old.len() bytes at offset and, if they equal old, overwrite them with new.

Returns Ok(true) if the comparison succeeded and the exchange was performed. Returns Ok(false) without modifying the file if old.len() != new.len() or if the current bytes do not match old.

Both the compare and the exchange happen under the same write lock.

§Feature flags

Only available when both the set and atomic Cargo features are enabled.

§Errors

Returns io::ErrorKind::InvalidInput if offset + old.len() overflows u64 or exceeds the current payload size. Propagates any I/O error from read_exact, write_all, or durable_sync.

Source

pub fn process<F>(&self, start: u64, end: u64, f: F) -> Result<()>
where F: FnOnce(&mut [u8]),

Read bytes in the half-open logical range [start, end), pass them to a callback that may mutate them in place, then write the modified bytes back.

The read, callback invocation, and write all happen under the same write lock, so no other thread can observe an intermediate state. The file size is never changed.

start == end is a valid no-op: f is called with an empty slice and no I/O is performed beyond the initial size check.

§Feature flags

Only available when both the set and atomic Cargo features are enabled.

§Errors

Returns io::ErrorKind::InvalidInput if end < start or if end exceeds the current payload size. Propagates any I/O error from read_exact, write_all, or durable_sync.

Source§

impl BStack

Source

pub fn len(&self) -> Result<u64>

Return the current logical payload size in bytes (excludes the 16-byte header).

Takes the read lock, so it can run concurrently with other len calls but blocks while any write-lock operation is in progress. The returned value always reflects a clean operation boundary.

§Errors

Propagates any io::Error from File::metadata.

Source

pub fn is_empty(&self) -> Result<bool>

Return true if the stack contains no payload bytes.

§Errors

Propagates any io::Error from File::metadata.

Source§

impl BStack

Source

pub fn reader(&self) -> BStackReader<'_>

Create a BStackReader positioned at the start of the payload.

Source

pub fn reader_at(&self, offset: u64) -> BStackReader<'_>

Create a BStackReader positioned at offset bytes into the payload.

Seeking past the current end is allowed; read will return Ok(0) until new data is pushed past that point.

Trait Implementations§

Source§

impl Debug for BStack

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<'a> From<&'a BStack> for BStackReader<'a>

Source§

fn from(stack: &'a BStack) -> Self

Converts to this type from the input type.
Source§

impl From<BStack> for LinearBStackAllocator

Source§

fn from(stack: BStack) -> Self

Converts to this type from the input type.
Source§

impl<'a> From<BStackReader<'a>> for &'a BStack

Source§

fn from(val: BStackReader<'a>) -> Self

Converts to this type from the input type.
Source§

impl From<LinearBStackAllocator> for BStack

Source§

fn from(alloc: LinearBStackAllocator) -> Self

Converts to this type from the input type.
Source§

impl Hash for BStack

Hashes the instance address, consistent with the pointer-identity PartialEq.

Source§

fn hash<H: Hasher>(&self, state: &mut H)

Feeds this value into the given Hasher. Read more
1.3.0 · Source§

fn hash_slice<H>(data: &[Self], state: &mut H)
where H: Hasher, Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
Source§

impl PartialEq for BStack

Two BStack instances are equal iff they are the same instance in memory.

Because BStack::open acquires an exclusive advisory lock, no two BStack values within one process can refer to the same file at the same time. Pointer identity is therefore the only meaningful equality: a stack is equal to itself and to nothing else.

Source§

fn eq(&self, other: &Self) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, and should not be overridden without very good reason.
Source§

impl Write for &BStack

Shared-reference counterpart of impl Write for BStack.

Because push takes &self (interior mutability via RwLock), the Write implementation is also available on &BStack, mirroring the standard library’s impl Write for &File.

Source§

fn write(&mut self, buf: &[u8]) -> Result<usize>

Writes a buffer into this writer, returning how many bytes were written. Read more
Source§

fn flush(&mut self) -> Result<()>

Flushes this output stream, ensuring that all intermediately buffered contents reach their destination. Read more
1.36.0 · Source§

fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> Result<usize, Error>

Like write, except that it writes from a slice of buffers. Read more
Source§

fn is_write_vectored(&self) -> bool

🔬This is a nightly-only experimental API. (can_vector)
Determines if this Writer has an efficient write_vectored implementation. Read more
1.0.0 · Source§

fn write_all(&mut self, buf: &[u8]) -> Result<(), Error>

Attempts to write an entire buffer into this writer. Read more
Source§

fn write_all_vectored(&mut self, bufs: &mut [IoSlice<'_>]) -> Result<(), Error>

🔬This is a nightly-only experimental API. (write_all_vectored)
Attempts to write multiple buffers into this writer. Read more
1.0.0 · Source§

fn write_fmt(&mut self, args: Arguments<'_>) -> Result<(), Error>

Writes a formatted string into this writer, returning any error encountered. Read more
1.0.0 · Source§

fn by_ref(&mut self) -> &mut Self
where Self: Sized,

Creates a “by reference” adapter for this instance of Write. Read more
Source§

impl Write for BStack

Appends bytes to the stack.

Each call to write is equivalent to push: all bytes are written atomically and durably synced before returning. Calling write_all or chaining multiple write calls therefore issues one durable_sync per call — callers that need to batch many small writes without per-write syncs should accumulate data and call push directly.

flush is a no-op because every write is already durable.

Source§

fn write(&mut self, buf: &[u8]) -> Result<usize>

Writes a buffer into this writer, returning how many bytes were written. Read more
Source§

fn flush(&mut self) -> Result<()>

Flushes this output stream, ensuring that all intermediately buffered contents reach their destination. Read more
1.36.0 · Source§

fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> Result<usize, Error>

Like write, except that it writes from a slice of buffers. Read more
Source§

fn is_write_vectored(&self) -> bool

🔬This is a nightly-only experimental API. (can_vector)
Determines if this Writer has an efficient write_vectored implementation. Read more
1.0.0 · Source§

fn write_all(&mut self, buf: &[u8]) -> Result<(), Error>

Attempts to write an entire buffer into this writer. Read more
Source§

fn write_all_vectored(&mut self, bufs: &mut [IoSlice<'_>]) -> Result<(), Error>

🔬This is a nightly-only experimental API. (write_all_vectored)
Attempts to write multiple buffers into this writer. Read more
1.0.0 · Source§

fn write_fmt(&mut self, args: Arguments<'_>) -> Result<(), Error>

Writes a formatted string into this writer, returning any error encountered. Read more
1.0.0 · Source§

fn by_ref(&mut self) -> &mut Self
where Self: Sized,

Creates a “by reference” adapter for this instance of Write. Read more
Source§

impl Eq for BStack

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.