From 6eb159eb6dacfddee3052add138af321e383efcf Mon Sep 17 00:00:00 2001 From: Schuyler Goodman Date: Tue, 24 Mar 2026 11:15:05 -0400 Subject: [PATCH 1/5] Move timed_async to tick --- crates/cachet/src/fallback.rs | 9 +- crates/cachet/src/refresh.rs | 5 +- crates/cachet/src/telemetry/ext.rs | 130 --------------------------- crates/cachet/src/telemetry/mod.rs | 1 - crates/cachet/src/wrapper.rs | 9 +- crates/tick/Cargo.toml | 4 + crates/tick/examples/timed_result.rs | 26 ++++++ crates/tick/src/clock.rs | 64 +++++++++++++ crates/tick/src/lib.rs | 2 + crates/tick/src/timed.rs | 45 ++++++++++ 10 files changed, 149 insertions(+), 146 deletions(-) delete mode 100644 crates/cachet/src/telemetry/ext.rs create mode 100644 crates/tick/examples/timed_result.rs create mode 100644 crates/tick/src/timed.rs diff --git a/crates/cachet/src/fallback.rs b/crates/cachet/src/fallback.rs index b775bbd58..6188e9012 100644 --- a/crates/cachet/src/fallback.rs +++ b/crates/cachet/src/fallback.rs @@ -17,7 +17,6 @@ use tick::Clock; use crate::Error; use crate::cache::CacheName; use crate::refresh::TimeToRefresh; -use crate::telemetry::ext::ClockExt; use crate::telemetry::{CacheActivity, CacheOperation, CacheTelemetry}; /// Type alias for promotion predicate functions. @@ -212,7 +211,7 @@ where /// /// Separated from [`get`](Self::get) to keep the hot path (primary hits) small. async fn get_from_fallback(&self, key: &K) -> Result>, Error> { - let timed = self.inner.clock.timed_async(self.inner.fallback.get(key)).await; + let timed = self.inner.clock.timed(self.inner.fallback.get(key)).await; self.inner .telemetry .record(self.inner.name, CacheOperation::Get, CacheActivity::Fallback, timed.duration); @@ -223,11 +222,7 @@ where if let Some(ref v) = fallback_value && self.inner.policy.should_promote(v) { - let timed_insert = self - .inner - .clock - .timed_async(self.inner.primary.insert(key.clone(), v.clone())) - .await; + let timed_insert = self.inner.clock.timed(self.inner.primary.insert(key.clone(), v.clone())).await; // Insert errors are intentionally swallowed - a failed promotion should not // fail the overall get. The CacheWrapper around the primary tier already // records an Error activity on insert failure. diff --git a/crates/cachet/src/refresh.rs b/crates/cachet/src/refresh.rs index b3369d5c7..c572a77ec 100644 --- a/crates/cachet/src/refresh.rs +++ b/crates/cachet/src/refresh.rs @@ -18,7 +18,6 @@ use cachet_tier::{CacheEntry, CacheTier}; use parking_lot::Mutex; use crate::fallback::{FallbackCache, FallbackCacheInner}; -use crate::telemetry::ext::ClockExt; use crate::telemetry::{CacheActivity, CacheOperation}; /// Configuration for background cache refresh. @@ -146,7 +145,7 @@ where F: CacheTier + Send + Sync + 'static, { pub(crate) async fn fetch_and_promote(&self, key: K) { - let timed = self.clock.timed_async(self.fallback.get(&key)).await; + let timed = self.clock.timed(self.fallback.get(&key)).await; match timed.result { Ok(Some(value)) => self.handle_fallback_hit(key, value, timed.duration).await, @@ -164,7 +163,7 @@ where } async fn promote_to_primary(&self, key: K, value: CacheEntry) { - let timed = self.clock.timed_async(self.primary.insert(key, value)).await; + let timed = self.clock.timed(self.primary.insert(key, value)).await; match timed.result { Ok(()) => { diff --git a/crates/cachet/src/telemetry/ext.rs b/crates/cachet/src/telemetry/ext.rs deleted file mode 100644 index 1f1c18bc6..000000000 --- a/crates/cachet/src/telemetry/ext.rs +++ /dev/null @@ -1,130 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -//! Extension traits for telemetry recording. - -use std::pin::Pin; -use std::task::{Context, Poll}; -use std::time::Duration; - -use pin_project_lite::pin_project; -use tick::{Clock, Stopwatch}; - -/// Result of a timed async operation. -#[derive(Debug, Clone, Copy)] -pub struct TimedResult { - /// The result of the operation. - pub result: R, - /// The duration of the operation. - pub duration: Duration, -} - -pin_project! { - /// A future that times the inner future's execution. - #[must_use = "futures do nothing unless polled"] - pub struct Timed { - #[pin] - inner: F, - watch: Stopwatch, - } -} - -impl Future for Timed { - type Output = TimedResult; - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let this = self.project(); - match this.inner.poll(cx) { - Poll::Ready(result) => Poll::Ready(TimedResult { - result, - duration: this.watch.elapsed(), - }), - Poll::Pending => Poll::Pending, - } - } -} - -/// Extension trait for timing async operations. -pub trait ClockExt { - /// Times an async operation and returns both the result and elapsed duration. - fn timed_async(&self, f: F) -> Timed - where - F: Future; -} - -impl ClockExt for Clock { - fn timed_async(&self, f: F) -> Timed - where - F: Future, - { - Timed { - inner: f, - watch: self.stopwatch(), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - fn block_on(f: F) -> F::Output { - futures::executor::block_on(f) - } - - #[test] - fn clock_ext_timed_async_measures_duration() { - block_on(async { - let control = tick::ClockControl::new(); - let clock = control.to_clock(); - - let timed = clock - .timed_async(async { - control.advance(Duration::from_millis(100)); - 42 - }) - .await; - - assert_eq!(timed.result, 42); - assert_eq!(timed.duration, Duration::from_millis(100)); - }); - } - - #[test] - fn clock_ext_timed_async_handles_pending() { - use std::pin::Pin; - use std::sync::Arc; - use std::sync::atomic::{AtomicBool, Ordering}; - use std::task::{Context, Poll}; - - /// A future that returns Pending on the first poll, then Ready on the second. - struct YieldOnce { - yielded: Arc, - } - - impl std::future::Future for YieldOnce { - type Output = i32; - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - if self.yielded.swap(true, Ordering::SeqCst) { - Poll::Ready(99) - } else { - cx.waker().wake_by_ref(); - Poll::Pending - } - } - } - - block_on(async { - let control = tick::ClockControl::new(); - let clock = control.to_clock(); - - let timed = clock - .timed_async(YieldOnce { - yielded: Arc::new(AtomicBool::new(false)), - }) - .await; - - assert_eq!(timed.result, 99); - }); - } -} diff --git a/crates/cachet/src/telemetry/mod.rs b/crates/cachet/src/telemetry/mod.rs index ac33a4713..caf990d53 100644 --- a/crates/cachet/src/telemetry/mod.rs +++ b/crates/cachet/src/telemetry/mod.rs @@ -10,7 +10,6 @@ pub(crate) mod attributes; pub(crate) mod cache; pub(crate) mod config; -pub(crate) mod ext; #[cfg(any(feature = "metrics", test))] pub(crate) mod metrics; diff --git a/crates/cachet/src/wrapper.rs b/crates/cachet/src/wrapper.rs index cf9c13b41..31eb4d043 100644 --- a/crates/cachet/src/wrapper.rs +++ b/crates/cachet/src/wrapper.rs @@ -14,7 +14,6 @@ use cachet_tier::CacheTier; use tick::Clock; use crate::cache::CacheName; -use crate::telemetry::ext::ClockExt; use crate::telemetry::{CacheActivity, CacheOperation, CacheTelemetry}; use crate::{CacheEntry, Error}; @@ -124,7 +123,7 @@ where CT: CacheTier + Send + Sync, { async fn get(&self, key: &K) -> Result>, Error> { - let timed = self.clock.timed_async(self.inner.get(key)).await; + let timed = self.clock.timed(self.inner.get(key)).await; match timed.result { Ok(value) => Ok(self.handle_get_result(value, timed.duration)), Err(e) => { @@ -137,7 +136,7 @@ where async fn insert(&self, key: K, mut entry: CacheEntry) -> Result<(), Error> { entry.ensure_cached_at(self.clock.system_time()); - let timed = self.clock.timed_async(self.inner.insert(key, entry)).await; + let timed = self.clock.timed(self.inner.insert(key, entry)).await; match &timed.result { Ok(()) => { self.telemetry @@ -155,7 +154,7 @@ where } async fn invalidate(&self, key: &K) -> Result<(), Error> { - let timed = self.clock.timed_async(self.inner.invalidate(key)).await; + let timed = self.clock.timed(self.inner.invalidate(key)).await; match &timed.result { Ok(()) => { self.telemetry @@ -173,7 +172,7 @@ where } async fn clear(&self) -> Result<(), Error> { - let timed = self.clock.timed_async(self.inner.clear()).await; + let timed = self.clock.timed(self.inner.clear()).await; match &timed.result { Ok(()) => { self.telemetry diff --git a/crates/tick/Cargo.toml b/crates/tick/Cargo.toml index ca3ec4186..a7789ab39 100644 --- a/crates/tick/Cargo.toml +++ b/crates/tick/Cargo.toml @@ -96,6 +96,10 @@ required-features = ["test-util"] name = "interop_jiff" required-features = ["test-util"] +[[example]] +name = "timed_result" +required-features = ["tokio"] + [[bench]] name = "clock_bench" harness = false diff --git a/crates/tick/examples/timed_result.rs b/crates/tick/examples/timed_result.rs new file mode 100644 index 000000000..b2b16a273 --- /dev/null +++ b/crates/tick/examples/timed_result.rs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! This example demonstrates advanced usage of the `Clock` API, including +//! `Stopwatch`, `PeriodicTimer`, and timeouts. + +use tick::Clock; +use tick::TimedResult; + +#[tokio::main] +async fn main() { + // Create a clock for the Tokio runtime. + let clock = Clock::new_tokio(); + + // Start some background work that returns a result after a delay. + let background_job = async { + clock.delay(std::time::Duration::from_millis(10)).await; + "Background job result" + }; + + // Use `Timed` to measure the time taken by the background job and capture its result. + let TimedResult { result, duration } = clock.timed(background_job).await; + + // Stop the measurement and print the elapsed time. + println!("Result: {}, Elapsed time: {:?}", result, duration); +} diff --git a/crates/tick/src/clock.rs b/crates/tick/src/clock.rs index b81532952..c2536104f 100644 --- a/crates/tick/src/clock.rs +++ b/crates/tick/src/clock.rs @@ -7,6 +7,7 @@ use std::time::{Duration, Instant, SystemTime}; use thread_aware::ThreadAware; use thread_aware::affinity::{MemoryAffinity, PinnedAffinity}; +use crate::Timed; use crate::state::ClockState; use crate::timers::TimerKey; @@ -459,6 +460,17 @@ impl Clock { crate::Stopwatch::new(self) } + #[must_use] + pub fn timed(&self, f: F) -> Timed + where + F: Future, + { + Timed { + inner: f, + watch: self.stopwatch(), + } + } + pub(super) fn register_timer(&self, when: Instant, waker: Waker) -> TimerKey { match self.clock_state() { #[cfg(any(feature = "test-util", test))] @@ -781,4 +793,56 @@ mod tests { insta::assert_debug_snapshot!(clock); } + + #[tokio::test] + async fn timed_measures_duration() { + let control = ClockControl::new(); + let clock = control.to_clock(); + + let timed = clock + .timed(async { + control.advance(Duration::from_millis(100)); + 42 + }) + .await; + + assert_eq!(timed.result, 42); + assert_eq!(timed.duration, Duration::from_millis(100)); + } + + #[tokio::test] + async fn timed_handles_pending() { + use std::pin::Pin; + use std::sync::Arc; + use std::sync::atomic::{AtomicBool, Ordering}; + use std::task::{Context, Poll}; + + /// A future that returns Pending on the first poll, then Ready on the second. + struct YieldOnce { + yielded: Arc, + } + + impl std::future::Future for YieldOnce { + type Output = i32; + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + if self.yielded.swap(true, Ordering::SeqCst) { + Poll::Ready(99) + } else { + cx.waker().wake_by_ref(); + Poll::Pending + } + } + } + + let control = ClockControl::new(); + let clock = control.to_clock(); + + let timed = clock + .timed(YieldOnce { + yielded: Arc::new(AtomicBool::new(false)), + }) + .await; + + assert_eq!(timed.result, 99); + } } diff --git a/crates/tick/src/lib.rs b/crates/tick/src/lib.rs index 3c8f6a2e7..56f58e9ca 100644 --- a/crates/tick/src/lib.rs +++ b/crates/tick/src/lib.rs @@ -251,6 +251,7 @@ mod periodic_timer; mod state; mod stopwatch; mod system_time_ext; +mod timed; mod timers; pub mod runtime; @@ -264,6 +265,7 @@ pub use future_ext::FutureExt; pub use periodic_timer::PeriodicTimer; pub use stopwatch::Stopwatch; pub use system_time_ext::SystemTimeExt; +pub use timed::{Timed, TimedResult}; pub use timeout::Timeout; /// Implements [`ThreadAware`](thread_aware::ThreadAware) for types that don't require any special relocation handling. diff --git a/crates/tick/src/timed.rs b/crates/tick/src/timed.rs new file mode 100644 index 000000000..1b8316560 --- /dev/null +++ b/crates/tick/src/timed.rs @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! Extension traits for telemetry recording. + +use std::pin::Pin; +use std::task::{Context, Poll}; +use std::time::Duration; + +use crate::Stopwatch; +use pin_project_lite::pin_project; + +/// Result of a timed async operation. +#[derive(Debug, Clone, Copy)] +pub struct TimedResult { + /// The result of the operation. + pub result: R, + /// The duration of the operation. + pub duration: Duration, +} + +pin_project! { + /// A future that times the inner future's execution. + #[must_use = "futures do nothing unless polled"] + pub struct Timed { + #[pin] + pub(crate) inner: F, + pub(crate) watch: Stopwatch, + } +} + +impl Future for Timed { + type Output = TimedResult; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.project(); + match this.inner.poll(cx) { + Poll::Ready(result) => Poll::Ready(TimedResult { + result, + duration: this.watch.elapsed(), + }), + Poll::Pending => Poll::Pending, + } + } +} From 2ff96e00a5e4f2ead04b68aff58d369294348da1 Mon Sep 17 00:00:00 2001 From: Schuyler Goodman Date: Tue, 24 Mar 2026 11:30:06 -0400 Subject: [PATCH 2/5] Add documentation --- crates/tick/examples/timed_result.rs | 9 ++++---- crates/tick/src/clock.rs | 20 ++++++++++++++++ crates/tick/src/timed.rs | 34 +++++++++++++++++++++++----- 3 files changed, 52 insertions(+), 11 deletions(-) diff --git a/crates/tick/examples/timed_result.rs b/crates/tick/examples/timed_result.rs index b2b16a273..ad33bd3b4 100644 --- a/crates/tick/examples/timed_result.rs +++ b/crates/tick/examples/timed_result.rs @@ -1,11 +1,10 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -//! This example demonstrates advanced usage of the `Clock` API, including -//! `Stopwatch`, `PeriodicTimer`, and timeouts. +//! This example demonstrates how to measure the execution time of an async +//! operation using [`Clock::timed`] and [`TimedResult`]. -use tick::Clock; -use tick::TimedResult; +use tick::{Clock, TimedResult}; #[tokio::main] async fn main() { @@ -21,6 +20,6 @@ async fn main() { // Use `Timed` to measure the time taken by the background job and capture its result. let TimedResult { result, duration } = clock.timed(background_job).await; - // Stop the measurement and print the elapsed time. + // Print the result and the elapsed time. println!("Result: {}, Elapsed time: {:?}", result, duration); } diff --git a/crates/tick/src/clock.rs b/crates/tick/src/clock.rs index c2536104f..f8e5f20f2 100644 --- a/crates/tick/src/clock.rs +++ b/crates/tick/src/clock.rs @@ -460,6 +460,26 @@ impl Clock { crate::Stopwatch::new(self) } + /// Wraps a future so that its execution time is measured. + /// + /// Returns a [`Timed`] future whose output is a [`TimedResult`][crate::TimedResult] + /// containing both the inner future's result and the elapsed duration. + /// + /// The measurement uses the same clock as the [`Stopwatch`][crate::Stopwatch], + /// so time can be controlled in tests via [`ClockControl`][crate::ClockControl]. + /// + /// # Examples + /// + /// ``` + /// use std::time::Duration; + /// + /// use tick::{Clock, TimedResult}; + /// + /// # async fn timed_example(clock: &Clock) { + /// let TimedResult { result, duration } = clock.timed(async { 42 }).await; + /// assert_eq!(result, 42); + /// # } + /// ``` #[must_use] pub fn timed(&self, f: F) -> Timed where diff --git a/crates/tick/src/timed.rs b/crates/tick/src/timed.rs index 1b8316560..2a543a31f 100644 --- a/crates/tick/src/timed.rs +++ b/crates/tick/src/timed.rs @@ -1,26 +1,48 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -//! Extension traits for telemetry recording. +//! Utilities for measuring the execution time of asynchronous operations. use std::pin::Pin; use std::task::{Context, Poll}; use std::time::Duration; -use crate::Stopwatch; use pin_project_lite::pin_project; -/// Result of a timed async operation. +use crate::Stopwatch; + +/// The result of a timed async operation, containing both the inner future's +/// output and the elapsed [`Duration`]. +/// +/// Produced by awaiting a [`Timed`] future, which is created via [`Clock::timed`][crate::Clock::timed]. +/// +/// # Examples +/// +/// ``` +/// use std::time::Duration; +/// +/// use tick::{Clock, TimedResult}; +/// +/// # async fn example(clock: &Clock) { +/// let TimedResult { result, duration } = clock.timed(async { 42 }).await; +/// assert_eq!(result, 42); +/// # } +/// ``` #[derive(Debug, Clone, Copy)] pub struct TimedResult { - /// The result of the operation. + /// The output of the inner future. pub result: R, - /// The duration of the operation. + /// The wall-clock duration of the operation. pub duration: Duration, } pin_project! { - /// A future that times the inner future's execution. + /// A future that wraps an inner future and measures its execution time. + /// + /// When the inner future completes, `Timed` yields a [`TimedResult`] containing + /// both the output and the elapsed [`Duration`]. + /// + /// Created via [`Clock::timed`][crate::Clock::timed]. #[must_use = "futures do nothing unless polled"] pub struct Timed { #[pin] From a315a974f0916efd17e68a2f531f7509b40fc11a Mon Sep 17 00:00:00 2001 From: schgoo <138131263+schgoo@users.noreply.github.com> Date: Wed, 25 Mar 2026 06:35:02 -0400 Subject: [PATCH 3/5] Update crates/tick/src/timed.rs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- crates/tick/src/timed.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/tick/src/timed.rs b/crates/tick/src/timed.rs index 2a543a31f..790f52652 100644 --- a/crates/tick/src/timed.rs +++ b/crates/tick/src/timed.rs @@ -32,7 +32,7 @@ use crate::Stopwatch; pub struct TimedResult { /// The output of the inner future. pub result: R, - /// The wall-clock duration of the operation. + /// The elapsed duration of the operation, measured using a monotonic clock. pub duration: Duration, } From 05a53abfff23535884577223210a87a12ca43496 Mon Sep 17 00:00:00 2001 From: Schuyler Goodman Date: Wed, 25 Mar 2026 11:10:08 -0400 Subject: [PATCH 4/5] Fix doctests, resolve clippy and comments --- crates/tick/Cargo.toml | 1 + crates/tick/src/clock.rs | 6 +++--- crates/tick/src/timed.rs | 3 +-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/tick/Cargo.toml b/crates/tick/Cargo.toml index a7789ab39..9b3e8c8d5 100644 --- a/crates/tick/Cargo.toml +++ b/crates/tick/Cargo.toml @@ -48,6 +48,7 @@ thread_aware.workspace = true tokio = { workspace = true, optional = true, features = ["time", "rt"] } [dev-dependencies] +#internal ohno = { workspace = true, features = ["app-err"] } #external diff --git a/crates/tick/src/clock.rs b/crates/tick/src/clock.rs index f8e5f20f2..46283a842 100644 --- a/crates/tick/src/clock.rs +++ b/crates/tick/src/clock.rs @@ -471,16 +471,14 @@ impl Clock { /// # Examples /// /// ``` - /// use std::time::Duration; - /// /// use tick::{Clock, TimedResult}; /// /// # async fn timed_example(clock: &Clock) { /// let TimedResult { result, duration } = clock.timed(async { 42 }).await; + /// println!("Result: {}, Duration: {:?}", result, duration); /// assert_eq!(result, 42); /// # } /// ``` - #[must_use] pub fn timed(&self, f: F) -> Timed where F: Future, @@ -815,6 +813,7 @@ mod tests { } #[tokio::test] + #[cfg_attr(miri, ignore)] async fn timed_measures_duration() { let control = ClockControl::new(); let clock = control.to_clock(); @@ -831,6 +830,7 @@ mod tests { } #[tokio::test] + #[cfg_attr(miri, ignore)] async fn timed_handles_pending() { use std::pin::Pin; use std::sync::Arc; diff --git a/crates/tick/src/timed.rs b/crates/tick/src/timed.rs index 790f52652..3b2530c3d 100644 --- a/crates/tick/src/timed.rs +++ b/crates/tick/src/timed.rs @@ -19,12 +19,11 @@ use crate::Stopwatch; /// # Examples /// /// ``` -/// use std::time::Duration; -/// /// use tick::{Clock, TimedResult}; /// /// # async fn example(clock: &Clock) { /// let TimedResult { result, duration } = clock.timed(async { 42 }).await; +/// println!("Result: {}, Duration: {:?}", result, duration); /// assert_eq!(result, 42); /// # } /// ``` From e21eeb3e0de792651b162327a3be91774764dd74 Mon Sep 17 00:00:00 2001 From: Schuyler Goodman Date: Fri, 27 Mar 2026 17:31:29 -0400 Subject: [PATCH 5/5] Fix comments/clippy --- crates/tick/examples/timed_result.rs | 2 +- crates/tick/src/clock.rs | 4 +++- crates/tick/src/timed.rs | 4 +++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/crates/tick/examples/timed_result.rs b/crates/tick/examples/timed_result.rs index ad33bd3b4..459d29f32 100644 --- a/crates/tick/examples/timed_result.rs +++ b/crates/tick/examples/timed_result.rs @@ -21,5 +21,5 @@ async fn main() { let TimedResult { result, duration } = clock.timed(background_job).await; // Print the result and the elapsed time. - println!("Result: {}, Elapsed time: {:?}", result, duration); + println!("Result: {result}, Elapsed time: {duration:?}"); } diff --git a/crates/tick/src/clock.rs b/crates/tick/src/clock.rs index 46283a842..62f7dcc32 100644 --- a/crates/tick/src/clock.rs +++ b/crates/tick/src/clock.rs @@ -471,12 +471,14 @@ impl Clock { /// # Examples /// /// ``` + /// use std::time::Duration; + /// /// use tick::{Clock, TimedResult}; /// /// # async fn timed_example(clock: &Clock) { /// let TimedResult { result, duration } = clock.timed(async { 42 }).await; - /// println!("Result: {}, Duration: {:?}", result, duration); /// assert_eq!(result, 42); + /// assert!(duration >= Duration::from_millis(0)); /// # } /// ``` pub fn timed(&self, f: F) -> Timed diff --git a/crates/tick/src/timed.rs b/crates/tick/src/timed.rs index 3b2530c3d..0b8444b4a 100644 --- a/crates/tick/src/timed.rs +++ b/crates/tick/src/timed.rs @@ -19,12 +19,14 @@ use crate::Stopwatch; /// # Examples /// /// ``` +/// use std::time::Duration; +/// /// use tick::{Clock, TimedResult}; /// /// # async fn example(clock: &Clock) { /// let TimedResult { result, duration } = clock.timed(async { 42 }).await; -/// println!("Result: {}, Duration: {:?}", result, duration); /// assert_eq!(result, 42); +/// assert!(duration >= Duration::from_millis(0)); /// # } /// ``` #[derive(Debug, Clone, Copy)]