How to measure how long something took
Short answer: grab an Instant::now() before the work, call .elapsed() after it:
use std::time::Instant;
fn main() { let data: Vec<u64> = (0..5_000_000).collect(); let start = Instant::now(); let sum: u64 = data.iter().sum(); let took = start.elapsed(); println!("sum: {sum}"); println!("took: {took:?}");}cargo run -qsum: 12499997500000took: 27.524msThe sum is the same on every run; the time is not — yours will be whatever this loop costs on your machine. start is an Instant, and elapsed() is the method almost every measurement needs. From a fresh project — cargo new timing-demo, then replace src/main.rs with each block below.
Why the wall clock is the wrong ruler
Section titled “Why the wall clock is the wrong ruler”SystemTime can do this too — take two readings, subtract — and the compiler will not stop you:
use std::time::{Duration, SystemTime};
fn main() { let t1 = SystemTime::now(); std::thread::sleep(Duration::from_millis(50)); let t2 = SystemTime::now(); match t2.duration_since(t1) { Ok(d) => println!("elapsed: {} ms", d.as_millis()), Err(e) => println!("clock went backwards: {e}"), }}elapsed: 60 msThe Err branch is the failure you can see: the wall clock is allowed to step backwards (NTP corrections, a manual change, a VM resuming from a snapshot), and when it steps between t1 and t2, the measurement fails outright. The worse failure is invisible — a clock that steps forward mid-measurement returns Ok with a number that is too large, and nothing in the type tells you. A stopwatch whose readings can be silently edited by the network is not a stopwatch.
Instant is the other clock in the standard library, and the one built for this. It is monotonic: it counts from an arbitrary fixed point (on this machine, boot), so nothing outside your process can change how much time passed between two readings.
Instant’s arithmetic is total
Section titled “Instant’s arithmetic is total”The same subtraction, done with Instant, never fails and never lies:
use std::time::{Duration, Instant};
fn main() { let earlier = Instant::now(); std::thread::sleep(Duration::from_millis(30)); let later = Instant::now(); println!("forward: {:?}", later.duration_since(earlier)); println!("backward: {:?}", earlier.duration_since(later)); println!("checked: {:?}", earlier.checked_duration_since(later)); println!("ordering: {}", earlier < later);}forward: 40.005625msbackward: 0nschecked: Noneordering: trueRead that middle pair carefully. Subtracting an Instant from a later one gives 0ns — duration_since saturates to zero instead of producing a negative duration the type cannot represent. That is the right answer for a stopwatch, but if the order matters to your logic, checked_duration_since is the version that tells you: it returns None when its argument is later, which is the Err-shaped information SystemTime gives and Instant converts into a plain zero. Ordering comparisons (<, >=) work directly, and between two Instants from the same process they always have an answer.
One property to keep in mind: an Instant is only meaningful on the machine that created it, and only until that machine reboots. Never store one in a database, send one over the network, or print one into a log you will read tomorrow — that is what SystemTime is for.
Timing something fast: repeat it and use black_box
Section titled “Timing something fast: repeat it and use black_box”A single call that takes microseconds is invisible to a stopwatch run once, and a loop timed naively measures the optimizer as much as the code: sum a range without using the sum, and the compiler is free to delete the whole loop. Two fixes, both in this block — repeat the work and average, and wrap the inputs and output in std::hint::black_box so the compiler has to treat them as unknown:
use std::hint::black_box;use std::time::{Duration, Instant};
fn main() { let mut times = Vec::new(); for _ in 0..5 { let start = Instant::now(); let mut total = 0u64; for i in 0..1_000_000u64 { total = total.wrapping_add(black_box(i)); } black_box(total); times.push(start.elapsed()); } let mean = times.iter().sum::<Duration>() / 5; for t in × { println!("{t:.3?}"); } println!("mean: {mean:.3?}");}7.811ms7.650ms7.648ms7.499ms7.530msmean: 7.628msThe black_box around i keeps every addition happening inside the loop; the one around total keeps the loop from being deleted entirely. Duration supports + and division by an integer, so the mean is ordinary arithmetic. The precision in {t:.3?} is the Duration debug format truncated to three digits — handy for readings you do not want printed as nanoseconds.
Working with the Duration you got
Section titled “Working with the Duration you got”elapsed() returns a Duration, and that type is where the measurement gets used:
use std::time::{Duration, Instant};
fn main() { let start = Instant::now(); std::thread::sleep(Duration::from_millis(75)); let took = start.elapsed(); println!("seconds: {:.6}", took.as_secs_f64()); println!("times two: {:?}", took * 2); println!("over budget: {}", took > Duration::from_millis(50)); let per_run = took / 3; println!("divided by 3: {per_run:.3?}");}seconds: 0.085011times two: 170.02275msover budget: truedivided by 3: 28.337msas_secs_f64 and as_millis are the two most common exits: the float form for logging and computing, the integer forms for display (as_millis truncates — 75.1 ms reports as 75). Comparisons work against literals built with Duration::from_millis, which is how a “took longer than 50 ms” budget check is written. Multiplication and division by integers are defined, so scaling and averaging stay one expression; and an Instant plus a Duration is another Instant, which is how deadline math (“give up if this is still running at start + 2s”) is built on the same types.
When SystemTime is the right clock
Section titled “When SystemTime is the right clock”None of this makes SystemTime bad — it makes it the wrong tool for measuring. The moment the value has to outlive the process, Instant is disqualified and the wall clock is the only option: how long did this job take as the logs see it, which run started before the deploy, what happened while the machine was asleep. Those questions are answered with SystemTime on both ends, and the fallibility you accept is the same one — a clock correction in the middle of a measurement is a fact about the timeline, not a bug in the stopwatch. Keep them apart: Instant for durations the process measures itself, SystemTime for timestamps that leave it.
- The previous article covered parsing and formatting — turning calendar strings into values and back with
chrono. - Turning a raw timestamp into an actual date is the next article in this chapter.
- Working with dates and time — the rest of the chapter.