Releases: imazen/enough
v0.4.4
What's changed
- MSRV lowered from 1.89 to 1.85 — the edition 2024 floor.
u64::is_multiple_of()replaced with%inalmost-enough'sDebouncedTimeout. - criterion replaced with zenbench for all benchmarks. Drops the only dev-dep requiring Rust 1.86+.
- Miri strict provenance —
enough-ffitests no longer use integer-to-pointer casts. - Comprehensive CI — added cargo-semver-checks, cargo-hack feature powerset, cargo-deny, Miri, WASM build, macOS Intel runner.
No API changes. Semver-compatible with 0.4.3.
Full diff: v0.4.3...v0.4.4
v0.4.3
What's New
almost-enough: DebouncedTimeout
A deadline wrapper that learns how fast check() is called and skips most Instant::now() reads. Useful when the caller adds a deadline but the library controls check frequency — neither side can optimize the other's hot path.
How it works: Starts by reading the clock every call, then adapts skip_mod so clock reads happen roughly every 100μs (configurable). Adapts immediately when calls slow down (safety), gradually when they speed up (stability).
Performance:
| Workload | WithTimeout | DebouncedTimeout |
|---|---|---|
| Tight loop (no work) | 59M checks/s | 648M checks/s (11x) |
| Jittery (~100ns avg) | 29M checks/s | 62M checks/s (2.1x) |
| Codec (4KB chunks) | 17.1 GiB/s | 18.1 GiB/s (equivalent) |
API:
use almost_enough::{Stopper, DebouncedTimeoutExt};
use std::time::Duration;
let stop = Stopper::new()
.with_debounced_timeout(Duration::from_secs(30));
// Pass to any library accepting impl StopAlso available: with_debounced_deadline(), with_target_interval(), tighten(), tighten_deadline().
Maximum deadline overshoot equals the target interval (default 100μs).
v0.4.1 — Stop::may_stop() and impl Stop for Option<T>
What's New
Stop::may_stop() — runtime no-op detection
New default method on the Stop trait. Returns true for real stop sources, false for Unstoppable. Lets callers behind dyn Stop skip check overhead in hot loops.
impl Stop for Option<T: Stop>
None is a no-op (check() always returns Ok(())), Some(inner) delegates. Combined with may_stop(), this enables:
fn process(stop: &dyn Stop) -> Result<(), StopReason> {
let stop = stop.may_stop().then_some(stop); // Option<&dyn Stop>
for chunk in data.chunks(1024) {
stop.check()?; // None → Ok(()), Some → one vtable dispatch
}
Ok(())
}BoxedStop::active_stop() — indirection collapsing
New inherent method returns Option<&dyn Stop> pointing directly at the concrete type inside the box, bypassing the BoxedStop wrapper. Eliminates one vtable dispatch per check() in hot loops.
OrStop composition
OrStop::may_stop() returns false only when both halves are no-ops (e.g., OrStop<Unstoppable, Unstoppable>).
Compatibility
All changes are additive with default implementations. Fully semver-compatible — no downstream code changes required.
Crates
enough0.4.1almost-enough0.4.1enough-ffi0.4.0 (compatible, gainsmay_stop()via dep update)enough-tokio0.5.0 (compatible, gainsmay_stop()via dep update)
v0.3.0
Changes
Breaking Changes
- Renamed
NevertoUnstoppablefor clarity - the new name better communicates that cooperative cancellation is not available Neveris now a deprecated type alias for backwards compatibility
Version Updates
- enough: 0.2.0 → 0.3.0
- almost-enough: 0.2.0 → 0.3.0
- enough-ffi: 0.2.1 → 0.3.0
- enough-tokio: 0.2.2 → 0.3.0