v0.9.11 — Plays well with others #8
jamesgober
announced in
Announcements
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
-
Release Notes for v0.9.11 - Plays well with others
Version 0.9.11 - 2026-05-14
Patch release responding to two issues filed against 0.9.10. bbqsrc in #6 flagged that the
as_slicesignature change in 0.9.7 was a semver violation against Cargo's pre-1.0 compatibility rules (^0.9.6resolves to<0.10.0, so 0.9.7 should not have shipped a breaking change). ararog asked for smol runtime support, which the tokio-lockedasyncfeature did not provide. Both fixed here, plus an opportunistic ecosystem polish pass:bytes::Bytesintegration,io::Read+io::Seekcursor, andAsFd/AsRawFd/AsHandle/AsRawHandletrait impls. Everything additive; no API breaks.Highlights
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.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_slicefromResult<&[u8]>toResult<MappedSlice<'_>>, changed iteratorItemfromResult<Vec<u8>>toMappedSlice<'a>, and flattenedfor_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.6users were silently upgraded into broken code.The break carried for four releases (0.9.7 through 0.9.10) without acknowledgement. The
cargo-semver-checksworkflow 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
asyncfeature implementation through 0.9.10 wrapped synchronous calls intokio::task::spawn_blockingand required a tokio runtime to drive the returned future. smol users callingmmap.update_region_async(...).awaitfrom 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::unblockinstead. Theblockingcrate 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 standardFutures. The cost of the swap is one extra dep (blockingplus 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_executorproves the case directly: it constructs ablock_onfromstd::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
--all-features(up from 127 in 0.9.10), 1 ignored (the unrelated hugepages-fallback test), 0 failed.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.[dependencies]); the dep tree is smaller despite the new additions.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
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
This discussion was created from the release v0.9.11 — Plays well with others.
Beta Was this translation helpful? Give feedback.
All reactions