Clock Injection — Testable Time
retry and repeat use a live clock by default. For deterministic tests, use the explicit-clock variants: retry_with_clock, repeat_with_clock, and their interruption-aware forms.
The Clock Trait
use std::time::{Duration, Instant};
use effectful::{Effect, Never};
trait Clock {
fn now(&self) -> Instant;
fn sleep(&self, duration: Duration) -> Effect<(), Never, ()>;
fn sleep_until(&self, deadline: Instant) -> Effect<(), Never, ()>;
}
Clock is monotonic-time oriented. Calendar time for logging is exposed separately through LiveClock::now_utc().
Production: LiveClock
use effectful::{LiveClock, ThreadSleepRuntime};
let live_clock = LiveClock::new(ThreadSleepRuntime);
LiveClock delegates sleeping and now() to a runtime.
Testing: TestClock
use std::time::{Duration, Instant};
use effectful::{Clock, TestClock};
let start = Instant::now();
let clock = TestClock::new(start);
assert_eq!(clock.now(), start);
clock.advance(Duration::from_secs(60));
assert_eq!(clock.now(), start + Duration::from_secs(60));
TestClock records pending sleeps. Advancing or setting time drops pending sleeps whose deadlines have elapsed.
Test Example
use std::sync::{Arc, atomic::{AtomicU32, Ordering}};
use std::time::{Duration, Instant};
use effectful::{Schedule, TestClock, retry_with_clock, run_blocking};
let clock = TestClock::new(Instant::now());
let attempts = Arc::new(AtomicU32::new(0));
let effect = retry_with_clock(
{
let attempts = attempts.clone();
move || failing_operation(attempts.clone())
},
Schedule::exponential(Duration::from_secs(1)).compose(Schedule::recurs(3)),
clock.clone(),
None,
);
let result = run_blocking(effect, ());
assert!(result.is_err());
assert_eq!(attempts.load(Ordering::Relaxed), 4); // initial + 3 retries
This test runs without sleeping in real time because TestClock::sleep only records deadlines.
Clock as a Service
For application logic that needs time directly, model the clock as a service in your environment. The scheduling helpers accept a clock value explicitly; your own services can do the same.