v0.9.11 — Plays well with others
Pre-releaseRelease Notes for v0.9.11 - Plays well with others
Version 0.9.11 - 2026-05-14
smol works now. The
asyncfeature is runtime-agnostic since 0.9.11 — drop tokio from your tree and driveupdate_region_async/flush_asyncstraight from smol, async-std, or any custom executor. See "Smol support, in detail" below for the implementation story; jump to the "Runtime-agnostic async" highlight for the quick version.
Patch release responding to two issues filed against 0.9.10. bbqsrc in #6 flagged that the as_slice signature change in 0.9.7 was a semver violation against Cargo's pre-1.0 compatibility rules (^0.9.6 resolves to <0.10.0, so 0.9.7 should not have shipped a breaking change). ararog asked for smol runtime support, which the tokio-locked async feature did not provide. Both fixed here, plus an opportunistic ecosystem polish pass: bytes::Bytes integration, io::Read + io::Seek cursor, and AsFd / AsRawFd / AsHandle / AsRawHandle trait impls. Everything additive; no API breaks.
Highlights
- Runtime-agnostic async (smol unlocked).
update_region_async,flush_async,flush_range_async, and themanager::async::*helpers now route throughblocking::unblockinstead oftokio::task::spawn_blocking. Existing tokio users see no API change. The futures drive to completion on any executor (tokio, smol, async-std, embassy on hosted, custom executors). Thetokiodirect dep is gone under--features async; the much smallerblockingcrate replaces it. The transitive dep tree shrinks notably. smol-based projects can finally use the async surface without dragging tokio in. - Compat shims for the 0.9.7 semver violation.
MemoryMappedFile::as_slice_bytes(offset, len) -> Result<&[u8]>mirrors the 0.9.6as_slicesignature exactly: returns&[u8]directly onReadOnlyandCopyOnWritemappings, returnsMmapIoError::InvalidModeonReadWritematching the 0.9.6 behavior. Code that was broken by the 0.9.7 return-type change recovers with a one-method-name rename.ChunkIteratorMut::for_each_mut_legacy<F, E>(F) -> Result<Result<(), E>>mirrors the 0.9.6 nested-Result signature. Internally uses the same single-held-write-guard loop as the flattenedfor_each_mut, so the H2 perf win from 0.9.7 is preserved.chunks_owned()(already shipped in 0.9.7) covers the iterator-item migration path.
bytes::Bytesintegration (feature = "bytes"). NewMemoryMappedFile::read_bytes(offset, len) -> Result<bytes::Bytes>plusFrom<MappedSlice<'_>>/From<&MappedSlice<'_>>forbytes::Bytes. One allocation + memcpy at the conversion boundary; the resultingBytesis mapping-lifetime-independent and travels freely through hyper / tower / tonic / axum / reqwest. Opt-in feature, zero cost when disabled.io::Read+io::Seekcursor.mmap.reader()returns anMmapReader<'_>that plugs the mapping into every parser / decoder that takes a genericR: Read:serde_json::from_reader,flate2::read::GzDecoder,tar::Archive::new,image::ImageReader::new, the standard library'sBufReader, etc. Bounds-checked under the hood.position()andset_position()for direct cursor control.AsFd+AsRawFd(Unix) andAsHandle+AsRawHandle(Windows) trait impls onMemoryMappedFile. The std-blessed way to hand the underlying OS handle to FFI code,nix,rustix,polling, etc., without going throughunmap.
The semver story
The 0.9.7 release changed as_slice from Result<&[u8]> to Result<MappedSlice<'_>>, changed iterator Item from Result<Vec<u8>> to MappedSlice<'a>, and flattened for_each_mut's closure return shape. Those were real wins (the iterator change alone is 13-475x faster than the old allocating form per the 0.9.10 measurements). The mistake was the version number: 0.9.6 → 0.9.7 is treated as a minor / patch bump by Cargo's resolver (Rust pre-1.0 semver rule: the leftmost non-zero digit acts as "major"), so ^0.9.6 users were silently upgraded into broken code.
The break carried for four releases (0.9.7 through 0.9.10) without acknowledgement. The cargo-semver-checks workflow added in 0.9.10 would have caught this exact case at PR time but did not exist yet in 0.9.7. The compat shims in this release give downstream callers a one-line recovery path. Apologies to bbqsrc and to anyone else whose 0.9.6 code stopped compiling on 0.9.7. The CHANGELOG and the README's new "Migrating from 0.9.6" section document the breaks and the recovery path explicitly so the issue is visible in the public record.
Smol support, in detail
The async feature implementation through 0.9.10 wrapped synchronous calls in tokio::task::spawn_blocking and required a tokio runtime to drive the returned future. smol users calling mmap.update_region_async(...).await from a smol-based application got a future that never executed because spawn_blocking depends on tokio's runtime being installed.
Since 0.9.11 we use blocking::unblock instead. The blocking crate is the runtime-agnostic primitive used by smol, async-fs, async-net, async-process, and other "small async" crates. tokio's own runtime drives futures from blocking just fine because they're standard Futures. The cost of the swap is one extra dep (blocking plus its small set of transitive deps), more than offset by dropping tokio's much larger tree.
The integration test async_surface_runs_under_a_non_tokio_executor proves the case directly: it constructs a block_on from std::thread::park (the same primitive smol and pollster use), drives the async methods through it, and verifies the bytes round-trip. No tokio runtime, no panic, full async functionality.
Tests
- 140 tests pass under
--all-features(up from 127 in 0.9.10), 1 ignored (the unrelated hugepages-fallback test), 0 failed. - 13 new tests in
tests/v0_9_11_additions.rscover every new method and the smol-compat validation. cargo build(default / no-default / all-features),cargo +1.75 build --all-features(MSRV),cargo fmt --check,cargo clippy --all-targets --all-features -D warnings,cargo doc --no-deps --all-features, andcargo audit --deny warningsall clean.- 124 transitive crates in the lockfile (was ~130 with tokio in
[dependencies]); the dep tree is smaller despite the new additions. - Banned-words scan zero hits.
Internals
tokioremoved from[dependencies]; added to[dev-dependencies]purely so the existing#[tokio::test]test suite continues to drive the runtime-agnostic async surface. Downstream consumers no longer pull tokio via--features async.blocking 1.6added under--features async.bytes 1added under--features bytes.
Notes
- MSRV unchanged at Rust 1.75.
- All 0.9.7-introduced API surface (
MappedSlice, the new iterator items, the flattenedfor_each_mut) remains the recommended path. The compat shims are explicitly migration aids; the rustdoc on each shim points back at the modern equivalent. bytes::Bytes::from(MappedSlice<'_>)is one allocation + memcpy. For zero-copy networking, callers should useas_slicedirectly and pass the borrowed&[u8]through the chain; theBytesconversion is the right call when ownership has to cross a thread or process boundary.
Full Changelog: v0.9.10...v0.9.11