Skip to content

v0.3.0 — The Mockable Clock

Pre-release
Pre-release

Choose a tag to compare

@jamesgober jamesgober released this 21 May 08:39
· 4 commits to main since this release

v0.3.0 — The Mockable Clock

The headline feature of clock-lib lands in this release: a Clock trait with two implementations — SystemClock for production and ManualClock for tests — so timing-driven code becomes deterministically testable without ever calling thread::sleep.

Highlights

  • Clock trait. A Send + Sync source of time exposing now() -> Monotonic and wall() -> Wall. Take it as a dependency anywhere your code reads the clock; substitute it in tests.
  • SystemClock. Zero-sized, Copy, const-constructible. Forwards directly to the OS readings already in the crate — nothing extra to pay over the free functions.
  • ManualClock. Lock-free advance via an AtomicU64 offset. The clock never moves on its own. Tests advance it forward in arbitrary increments and observe the timing-driven behavior exactly.
  • Blanket impls. Clock is implemented for Arc<C> and &C where C: Clock, so shared ownership and borrowed access work without re-implementing the trait.
  • TTL test pattern, no sleep. The classic "is this expired?" check now takes 8 lines and zero milliseconds of real wall-clock time:
use clock_lib::{Clock, ManualClock, Monotonic};
use std::time::Duration;

fn expired<C: Clock>(clock: &C, stamp: Monotonic, ttl: Duration) -> bool {
    clock.now().duration_since(stamp) >= ttl
}

let clock = ManualClock::new();
let stamp = clock.now();
assert!(!expired(&clock, stamp, Duration::from_secs(60)));
clock.advance(Duration::from_secs(60));
assert!(expired(&clock, stamp, Duration::from_secs(60)));

What's New

Added

  • Clock trait with now and wall methods (Send + Sync).
  • SystemClock struct (Copy, const-constructible).
  • ManualClock struct with new, advance, and offset methods backed by an atomic offset.
  • Blanket Clock impls for Arc<C: Clock> and &C: Clock.
  • Integration tests covering: zero-advance idempotence, exact-advance semantics on both monotonic and wall, offset accumulation, the sleep-free TTL pattern, Arc<ManualClock> dispatch, and Arc<dyn Clock> trait-object usage.
  • Doctests on every public item in the new module.

Changed

  • Monotonic and Wall fields are now pub(crate) (internal change — field access remains forbidden outside the crate, but ManualClock can construct synthetic readings).

Compatibility

  • MSRV: Rust 1.85 (unchanged)
  • Edition: 2024 (unchanged)
  • Public API: strictly additive. All 0.2.x code continues to compile unchanged.

Upgrading From 0.2.x

[dependencies]
clock-lib = "0.3"

No code changes required. To adopt the mockable clock pattern, take a C: Clock (or &dyn Clock, or Arc<dyn Clock>) as a dependency wherever you call clock_lib::now() today, and pass SystemClock in production. Your tests can then swap to ManualClock and drop every thread::sleep.

What's Next

  • 0.4.0 — performance verification. Criterion benchmarks for reading latency on both SystemClock and ManualClock, with committed baselines proving zero overhead over raw std::time.
  • 0.5.0 — no_std hardening and the 1.0 release candidate.

Full Changelog: v0.2.1...v0.3.0