Qubit Clock
Time is a hidden dependency. Code that calls SystemTime::now(),
Instant::now(), or a sleep function directly is tied to the machine clock:
tests must really wait, boundary cases are hard to reach, and clock changes can
make results nondeterministic.
qubit-clock turns time into an injectable dependency. Application components
depend on small clock and timer traits; the composition root supplies standard
implementations in production and fixed or manually advanced implementations
in tests. The same business code runs in both environments, with no test-only
branch and no real delay.
Detailed documentation:
A first example
This session records a monotonic deadline instead of reading the global clock.
Its constructor accepts Arc<dyn MonotonicClock>, so the caller chooses how
time progresses:
use ;
use ;
The test covers an exact 30-second boundary without sleeping for 30 seconds.
Only the assembly changes; Session contains no mock flag or test-specific
logic.
Components at a glance
| Need | Trait | Real time | Deterministic tests |
|---|---|---|---|
| Externally meaningful timestamps | WallClock |
StdWallClock |
FixedWallClock, ManualWallClock |
| Elapsed time and deadlines | MonotonicClock |
StdMonotonicClock, TokioMonotonicClock |
ManualMonotonicClock |
| Async deadlines | Timer |
StdTimer, TokioTimer |
ManualTimer |
| Blocking waits | BlockingSleeper adapter |
compose a timer with independent progress | compose a manually driven timer |
Wall-clock time may jump and is intended for externally meaningful timestamps.
Monotonic time never moves backward within one clock domain and is intended for
elapsed time, retries, and timeouts. Every clock creates a same-domain timer
directly with clock.new_timer().
Installation
[]
= "0.13"
Enable the tokio feature when you need Tokio-backed clock and timer types and
their runtime-related errors:
[]
= { = "0.13", = ["tokio"] }
The tokio feature exposes TokioMonotonicClock, TokioTimer, and their
runtime-related errors. Manual time is executor-neutral and does not require
this feature. Tests that need deterministic timer failures can enable the
default-off test-util feature in a development dependency.
Timers and waits
Inject Arc<dyn Timer> when a component must await a deadline instead of only
checking the current time. Timer::after creates a relative deadline and
Timer::at accepts an absolute MonotonicInstant. A
ManualMonotonicClock creates a same-domain manual timer, so tests can advance
logical time instead of waiting for a scheduler or the operating system.
BlockingSleeper adapts a timer for synchronous code. Its timer backend must
be able to progress independently while the calling thread is parked. The
user guide covers manual-time coordination, Tokio
runtime ownership, wall-clock projection, cancellation, and error handling.
Use in related libraries
The same injection model is used by rs-command to enforce command timeouts
without real waits and by rs-id to separate ID timestamps from allocation
waits. rs-lock uses it to test timeout-aware waits, while rs-retry uses it
to test retry delays, attempt timeouts, and elapsed-time budgets. These
libraries inject the narrow capability they need (WallClock, Timer, or
MonotonicClock); their production code does not contain a separate mock
waiting algorithm.
Testing
# Run tests with the default feature set
# Run tests with all declared features
# Project CI checks
# Check code coverage
License
Copyright (c) 2025 - 2026. Haixing Hu. All rights reserved.
Licensed under the Apache License, Version 2.0. See LICENSE for the full license text.
Contributing
Contributions are welcome. Please follow the Rust API guidelines, keep public
API documentation and tests current, and run ./align-ci.sh to format code and
./ci-check.sh to satisfy CI requirements before submitting a pull request.
Author
Haixing Hu - Qubit Co. Ltd.
Repository: https://github.com/qubit-ltd/rs-clock