Use case · Wiring
Inject the clock into an engine
Construct one clock at the composition root and thread it through the engine as Arc<dyn Clock>. Every time read goes through it — which is exactly what makes the later swap to a virtual timeline a no-op for the rest of the code.
Construct once, share widely
The clock is a dependency, not an ambient. Build it where you wire the engine together, and pass clones of the Arc to each component that needs time.
use std::sync::Arc;
use ferro_replay::{Clock, SystemClock};
let clock: Arc<dyn Clock> = Arc::new(SystemClock::new());
let producer = Producer::new(clock.clone());
let consumer = Consumer::new(clock.clone());
let heartbeat = Heartbeat::new(clock.clone());Read time through the trait
Inside a component, store the clock and read through it. Nothing reaches for SystemTime or Instant directly.
use std::sync::Arc;
use std::time::Duration;
use ferro_replay::Clock;
struct Consumer {
clock: Arc<dyn Clock>,
}
impl Consumer {
async fn run(&self) {
let started_ns = self.clock.now_ns();
// I/O-timeout race / retry backoff — relative sleep.
self.clock.sleep(Duration::from_millis(250)).await;
let elapsed_ns = self.clock.now_ns() - started_ns;
// ...
}
}Wrap waits in a biased tokio::select! with your shutdown signal. sleep_until is cancel-safe: a cancelled arm drops the wait cleanly and re-arming the same deadline still fires correctly.
The payoff
Once time is injected, switching to deterministic replay or a test is a single substitution at the root — see Drive virtual time and Deterministic replay. The wallclock gate is what guarantees no component quietly broke the contract.