pub trait WalStore: Send + Sync {
// Required methods
fn write_at(&self, offset: u64, bytes: &[u8]) -> Result<()>;
fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<usize>;
fn truncate(&self, len: u64) -> Result<()>;
fn sync(&self) -> Result<()>;
fn len(&self) -> Result<u64>;
// Provided methods
fn is_empty(&self) -> Result<bool> { ... }
fn head(&self) -> Result<u64> { ... }
fn truncate_before(&self, _offset: u64) -> Result<u64> { ... }
}Expand description
A byte-addressable, append-only store with an explicit durability barrier.
The log treats a store as a growing array of bytes. It writes framed records
at reserved offsets — possibly from several threads concurrently and out of
order — reads from arbitrary offsets during recovery, and occasionally
truncates a torn tail. The one guarantee the log cannot provide itself — that
written bytes have reached stable storage — is delegated to
sync.
§Implementing a backend
The contract an implementation must honour:
write_atwritesbytesatoffset, growing the store ifoffsetis past the current end and zero-filling any gap (so a later offset written before an earlier one leaves detectable zero bytes in between, exactly as a sparse file does). Concurrent calls to disjoint ranges must not corrupt each other.read_atfillsbufstarting atoffset, returning the number of bytes read. It returns fewer thanbuf.len()only when the store ends first — that short read is how recovery detects a torn tail.syncreturns only once every prior write is durable.truncatediscards everything at or afterlen.
Send + Sync is required so the log can be shared across threads.
§Examples
use wal_db::{MemStore, Wal};
let wal = Wal::with_store(MemStore::new())?;
wal.append(b"record")?;
wal.sync()?;Required Methods§
Sourcefn write_at(&self, offset: u64, bytes: &[u8]) -> Result<()>
fn write_at(&self, offset: u64, bytes: &[u8]) -> Result<()>
Write bytes at byte offset, growing the store and zero-filling any
gap if offset is beyond the current end.
Sourcefn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<usize>
fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<usize>
Read into buf starting at byte offset, returning the number of bytes
read.
A return value smaller than buf.len() means the store ended before
buf could be filled.
Sourcefn truncate(&self, len: u64) -> Result<()>
fn truncate(&self, len: u64) -> Result<()>
Discard every byte at or after len, shrinking the store to exactly
len bytes.
Provided Methods§
Sourcefn is_empty(&self) -> Result<bool>
fn is_empty(&self) -> Result<bool>
Whether the store holds no bytes.
The default defers to len; override it only if a
backend can answer more cheaply.
Sourcefn head(&self) -> Result<u64>
fn head(&self) -> Result<u64>
The lowest offset that still holds data.
Normally 0. A backend that can drop a prefix (see
truncate_before) reports the offset of its
first surviving byte here, so recovery knows where to begin scanning. The
default backends that cannot drop a prefix leave this at 0.
Sourcefn truncate_before(&self, _offset: u64) -> Result<u64>
fn truncate_before(&self, _offset: u64) -> Result<u64>
Discard storage entirely below offset, if the backend can, returning the
new head.
Offsets are preserved: dropping a prefix never renumbers what remains, so
a record keeps its byte position (its LSN) for life. A backend that cannot
remove a prefix — a single file, where the surviving bytes would have to
move — leaves the store unchanged and returns its current head. One that
can (a segmented store, by deleting whole leading segment files) removes
what it can at its own granularity and returns the resulting head, which
may be below offset.
Dyn Compatibility§
This trait is dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".