v0.9.8 — Surface Finish
Pre-releaseRelease Notes for v0.9.8 - Surface Finish
Version 0.9.8 - 2026-05-12
The ergonomic pass. Eight new methods land on the public surface, all additive, all carrying their full safety contract and a working code example. open_or_create answers the most common question every user asks first ("how do I open if it's there and create if it's not?") with one call. from_file is the escape hatch for callers who already opened the file with their own OpenOptions (O_DIRECT, O_NOATIME, an inherited fd from a parent process) and want to mmap it without re-opening. unmap is the inverse: consume the mapping, drop the underlying memory in safe order, hand back the bare File so the caller can keep using it. flush_policy and pending_bytes expose the durability accumulator for diagnostics and dashboards. as_ptr / as_mut_ptr are the FFI escape hatches for handing the mapping to a C library by base pointer + length. prefetch_range issues posix_fadvise(POSIX_FADV_WILLNEED) on Linux to warm the page cache from the file side, complementary to the existing advise(WillNeed) that warms it from the VM side.
Under the surface, a real bug closed in the time-based flusher: a Duration subtraction that could underflow and panic if thread::sleep overshot under heavy scheduler contention. Fixed with saturating_sub. Bounds-check helpers (ensure_in_bounds, slice_range) and the small-and-hot accessors (len, is_empty, mode, flush_policy, pending_bytes) gained #[inline] so the optimiser can fold them into the call site every time. The two-branch bounds check merged into a single saturating_add comparison. Audit items E1, E2, E6, E7, F2, F5, and F9 all close in this release.
Highlights
open_or_create(path, default_size)opens the file if it exists, creates it atdefault_sizebytes if it does not. The existing-file path ignoresdefault_sizeand keeps the file's current length. The builder gets a matching terminal method:MemoryMappedFile::builder(path).mode(...).size(...).flush_policy(...).open_or_create().from_file(file, mode, path)wraps a pre-openedstd::fs::Filein aMemoryMappedFile. Callers needing customOpenOptions(Direct I/O, no-atime, security descriptors, inherited file descriptors) construct theFilethemselves and hand it off. Thepathargument is informational forpath()/ error messages; no syscall happens on it.unmap(self) -> Result<File, Self>consumes the mapping and returns the underlyingFile. Drop order is enforced: the background flusher stops first, then the mapping releases its virtual address space, then the file handle is yielded to the caller. ReturnsErr(self)unchanged if otherMemoryMappedFileclones are alive (theFileis shared viaArc<Inner>and cannot be extracted while other handles hold references).flush_policy()/pending_bytes()are#[inline]O(1)accessors over the durability accumulator. Useful for observability dashboards on long-running writers: pollpending_bytes()to see how close you are to the next auto-flush underEveryBytes/EveryWrites.unsafe fn as_ptr(&self) -> *const u8andunsafe fn as_mut_ptr(&self) -> Result<*mut u8>expose raw base pointers for FFI use cases that need aconst void */void *plus length. The rustdoc spells out the safety contract: do not dereference pastlen(), do not hold the pointer acrossresize(), do not alias the mutable pointer with any live Rust&reference to the same bytes.prefetch_range(offset, len)issuesposix_fadvise(POSIX_FADV_WILLNEED)against the file descriptor on Linux (and Android), kicking off kernel-side readahead. Documented as a no-op on other platforms. Bounds-checked. Complementary toadvise(MmapAdvice::WillNeed): file-side readahead viaposix_fadviseversus VM-side viamadvise. Issuing both helps cold reads of huge files.- 17 new integration tests in
tests/ergonomic_api.rscover every new method, both happy and error paths: open_or_create on both create / open paths, builder open_or_create on both, from_file across RO / RW / zero-length, unmap unique vs shared, flush_policy default vs explicit, pending_bytes through a threshold-crossing write, as_ptr / as_mut_ptr roundtrips againstread_into, and prefetch_range in-bounds / OOB / zero-length.
Performance
- Bounds-check helpers
#[inline]-ed.ensure_in_boundsandslice_rangeare called from every bounds-checked public method (as_slice,as_slice_mut,read_into,update_region,flush_range,touch_pages_range,prefetch_range, advise, lock, segment access). Inlining removes the function-call boundary on every read/write. The two-branch bounds check also collapsed into a singlesaturating_addcomparison: the previous form didif offset > totalthenif offset + len > total; the new form checksoffset + len > total || offset > totalin one expression (saturating-add catches the overflow case the first branch was redundantly guarding against). - Hot-path accessors marked
#[inline]:len(),is_empty(),mode(),flush_policy(),pending_bytes(). All trivial (one field read or one lock read of au64), so inlining is a clear win. align_upmarked#[inline]: called fromflush_range(microflush page alignment),touch_pages_range, and prefetch alignment paths. Trivial bit math.
Bug fix
flush::TimeBasedFlusherDuration underflow. The thread loop computed the next sleep slice asshutdown_poll.min(interval - elapsed). Ifthread::sleepovershot (heavy scheduler contention, system suspend/resume, oversubscribed runtime)elapsedcould exceedintervaland the subtraction would panic on Duration underflow, killing the flusher thread. Fixed by usinginterval.saturating_sub(elapsed): when overshoot occurs the remaining slice clamps to zero and the loop yields immediately, re-checks the shutdown flag, and either fires the callback or exits. No behavioural change in the common case.
Tests
- 121 tests pass under
--all-features(up from 101 in 0.9.7), 4 ignored (3 polling-watch tests gated on Windows mtime granularity, 1 hugepages fallback), 0 failed. - CI matrix combos:
--no-default-featuresand--no-default-features --features "cow locking advise"both clean locally; doctest counts grew from 13 to 16 with the new method examples. - Banned-words scan zero hits.
cargo fmt --checkclean.cargo clippy --all-targets --all-features -D warningsclean on default lints. - MSRV unchanged at Rust 1.75.
cargo +1.75 build --all-featuresclean.
Documentation
Cargo.tomlSEO sweep. Description leads with the unique selling point ("Zero-copy memory-mapped file I/O for Rust"), names the supported platforms, and lists the concrete use cases (databases, log structures, caches, game runtimes, IPC). Keywords tightened to the five highest-volume search terms:mmap,memory-mapped,zero-copy,filesystem,io. Categories:filesystem,data-structures,concurrency,database-implementations.- README opening rewritten around the actual differentiators: zero-copy on every mode, zero-allocation iteration, lock-free atomic views, configurable durability, the C1/C2/C3 audit closures. The "Quick start" snippet now shows
open_or_createas the everyday pattern alongsideopen_ro. docs/API.mdhas full sections for all eight new methods, TOC updated, install snippets bumped to 0.9.8, Version History entry added.REPS.mdsection 4 now lists every public method, including the new ones with// Since 0.9.8markers and the builder addition.
Notes
- No new runtime dependencies. Linux
posix_fadviseuses the already-requiredlibccrate. - MSRV unchanged at Rust 1.75.
MappedSliceandMappedSliceMutare re-exported from the crate root since 0.9.7; that has not changed.- The
unsafeexposure of raw base pointers viaas_ptr/as_mut_ptris a deliberate FFI escape hatch. The Rust API surface remains safe; the unsafe marker on those two methods forces callers to acknowledge the documented contract.
Deferred (with documented reason)
- F1 (anonymous shared-memory mapping)
new_anonymous(size)is the one item from the audit's ergonomic / functionality cluster that did not land in this release. The reason is structural: anonymous mappings have no backingFileand no meaningfulPath, so adopting them requires changingInner.file: FiletoOption<File>and threading sentinel-path handling through the resize / prefetch / async-flush paths. That refactor is sized for its own focused milestone rather than rolled into the ergonomic pass. - H3 (lock-free RW reads via arc-swap or
UnsafeCelldesign). The currentRwLock<MmapMut>design is sound and bounded; reads on RW mappings are concurrent across readers thanks to parking_lot's RwLock. Replacing the lock entirely is a memory-model question (do we accept torn reads from concurrent intra-process writers?), not a tuning question, and is rescoped to a 1.0 design conversation. - Native watch backends (inotify / FSEvents /
ReadDirectoryChangesW) and fuzz / MIRI runs remain on the roadmap for 0.9.9 and 0.9.10 respectively.
Full Changelog: v0.9.7...v0.9.8