Skip to content

Releases: imazen/enough

v0.4.4

12 Apr 06:03

Choose a tag to compare

What's changed

  • MSRV lowered from 1.89 to 1.85 — the edition 2024 floor. u64::is_multiple_of() replaced with % in almost-enough's DebouncedTimeout.
  • criterion replaced with zenbench for all benchmarks. Drops the only dev-dep requiring Rust 1.86+.
  • Miri strict provenanceenough-ffi tests 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

29 Mar 02:02

Choose a tag to compare

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 Stop

Also 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>

23 Mar 18:24

Choose a tag to compare

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

  • enough 0.4.1
  • almost-enough 0.4.1
  • enough-ffi 0.4.0 (compatible, gains may_stop() via dep update)
  • enough-tokio 0.5.0 (compatible, gains may_stop() via dep update)

v0.3.0

14 Jan 00:24

Choose a tag to compare

Changes

Breaking Changes

  • Renamed Never to Unstoppable for clarity - the new name better communicates that cooperative cancellation is not available
  • Never is 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