How to Use std

:time::Instant for Measuring Elapsed Time

Use `std::time::Instant` to measure the duration of a code block by calling `Instant::now()` at the start and `.elapsed()` at the end, which returns a `Duration` object.

When wall-clock time lies

You are profiling a function that feels sluggish. You grab a timestamp, run the code, grab another timestamp, and subtract. The result says the function took -5 seconds. Your code did not travel back in time. The system clock just got adjusted by NTP, or the laptop lid closed and the clock drifted. Wall-clock time is a liar when you care about performance. You need a clock that only moves forward.

Rust provides std::time::Instant for this exact problem. It is a monotonic clock. The value never goes backward. It ignores system clock adjustments, time zone changes, and leap seconds. If you measure a duration with Instant, the result is always positive and accurate relative to the program's execution.

The monotonic guarantee

Think of Instant as a stopwatch and SystemTime as a wall clock. The wall clock shows the time of day. Someone can wind it back, set it to the wrong year, or the battery can die and reset it. The stopwatch only counts up. It starts at zero and ticks forward. If the wall clock jumps, the stopwatch keeps ticking.

Operating systems adjust the system clock constantly. Network Time Protocol daemons drift-correct the clock. Virtual machines might pause and resume, causing the host clock to jump. A user might change the time zone or set the clock manually. Every one of these events can make SystemTime jump forward or backward. Instant reads from a hardware counter that only increments. It is the only safe way to measure elapsed time.

Measuring a single block

The idiomatic pattern is to capture the start, do work, and call elapsed(). The elapsed() method returns a Duration, which holds the time difference as seconds and nanoseconds.

use std::time::Instant;

fn main() {
    // Capture the starting point. This is a snapshot of the monotonic clock.
    let start = Instant::now();

    // Simulate work.
    let mut sum = 0;
    for i in 0..1_000_000 {
        sum += i;
    }

    // Calculate how much time passed since `start`.
    // This queries the clock again and computes the difference.
    let duration = start.elapsed();
    println!("Took {:?}", duration);
    println!("Milliseconds: {} ms", duration.as_millis());
}

Convention aside: calling start.elapsed() is preferred over Instant::now() - start. Both compile to similar code, but elapsed() communicates intent clearly. It reads as "how much time has elapsed?" rather than a raw subtraction.

Use Instant for durations. Use SystemTime for timestamps. Mixing them up is the fastest way to get negative durations.

Multi-phase checkpoints

Real code often has multiple phases. You might want to log how long validation took versus how long computation took. Keep the original Instant alive and compute deltas as you go. Dropping the Instant does not stop the clock, but it stops you from measuring the finish line.

use std::time::Instant;

fn process_batch(data: &[u64]) {
    // Start the stopwatch at the entry point.
    let start = Instant::now();

    // Phase 1: Validate inputs.
    for &val in data {
        if val == 0 {
            panic!("Zero values are not allowed");
        }
    }
    // Checkpoint: How long did validation take?
    let validation_time = start.elapsed();
    println!("Validation completed in {:?}", validation_time);

    // Phase 2: Heavy computation.
    let results: Vec<u64> = data.iter().map(|&x| x * x).collect();

    // Final check: Total time.
    let total_time = start.elapsed();
    println!("Total processing time: {:?}", total_time);

    // Calculate the delta for the computation phase.
    let computation_time = total_time - validation_time;
    println!("Computation phase took {:?}", computation_time);
}

The Duration type supports subtraction. You can subtract one Duration from another to get a smaller Duration. This is safe and will not panic. It saturates at zero if the result would be negative.

Keep your Instant alive. Dropping it doesn't stop the clock, but it stops you from measuring the finish line.

Pitfalls and panics

The most common mistake is subtracting instants in the wrong order. Instant implements subtraction, but it enforces that the left operand is later than the right operand. If you subtract start - end, the program panics.

use std::time::Instant;

fn main() {
    let start = Instant::now();
    // ... do work ...
    let end = Instant::now();

    // BAD: This panics at runtime.
    let duration = start - end;
}

The program panics with a subtraction overflow error. The compiler cannot catch this because the order depends on runtime values. Always subtract end - start.

Another trap is mixing Instant with SystemTime. They are different types representing different clocks. You cannot subtract them.

use std::time::{Instant, SystemTime};

fn main() {
    let instant = Instant::now();
    let sys = SystemTime::now();

    // BAD: Type mismatch.
    let _ = instant - sys;
}

The compiler rejects this with E0308 (mismatched types). You cannot subtract a system time from an instant. If you need both, store them separately and use them for their intended purposes.

Subtract end - start. Never the other way around. The compiler won't save you from the panic, but the math will.

Performance traps

Calling elapsed() is not free. It queries the underlying system clock, which may involve a syscall or a futex read. In a tight loop, calling elapsed() every iteration adds measurable overhead. Do not measure inside the hot path. Cache the instant and compute differences only when you need to report.

use std::time::Instant;

fn heavy_loop() {
    let start = Instant::now();
    let mut last_check = start;

    for i in 0..10_000_000 {
        // Do work here.
        // BAD: let _ = start.elapsed(); // Expensive!

        if i % 1_000_000 == 0 {
            let now = Instant::now();
            let delta = now - last_check;
            println!("Checkpoint {}: {:?}", i, delta);
            last_check = now;
        }
    }
}

This pattern minimizes clock queries. You only touch the clock at checkpoints. The inner loop runs without measurement overhead.

Don't call elapsed() in a hot loop. Cache the instant and compute deltas only when you need to report.

Duration arithmetic

The Duration type holds time as a pair of numbers: whole seconds and remaining nanoseconds. It provides methods to convert to different units. Be careful with precision loss.

The method as_secs_f64() returns a floating-point number. Floating-point numbers lose precision for large values. A duration of several days will lose nanosecond precision when converted to f64. Use as_secs() and subsec_nanos() if you need exact integer arithmetic.

The method as_nanos() returns a u128. This avoids overflow for very long durations. A u64 would overflow after about 584 years. u128 gives you plenty of headroom.

Convention aside: when logging durations, prefer as_millis() or as_secs_f32() over as_secs_f64(). Milliseconds are usually sufficient for logs, and f32 is cheaper to format. Reserve as_nanos() for high-precision profiling where you need the full resolution.

Async considerations

In async code, std::time::Instant still works for measurement. However, async runtimes like Tokio provide tokio::time::Instant for scheduling timers. The standard library instant measures time; the runtime instant schedules work. They often wrap the same underlying clock, but the types are distinct to prevent mixing measurement with scheduling logic.

If you are using Tokio, use tokio::time::Instant for sleep and timeouts. Use std::time::Instant for profiling. The runtime types integrate with the executor's waker system, allowing efficient scheduling without polling.

Decision matrix

Use Instant when measuring elapsed time, performance profiling, or timeouts. The monotonic guarantee ensures your measurements never go backward.

Use SystemTime when you need the actual calendar time, such as logging an event with a date or setting a file modification timestamp.

Use Duration when you need to represent a quantity of time independent of any clock, like a configuration value for a retry delay or a sleep interval.

Use tokio::time::Instant when scheduling async tasks or sleeping in a Tokio runtime. It integrates with the executor for efficient waiting.

If you are measuring time, reach for Instant. If you are recording time, reach for SystemTime. The names tell you exactly what to do.

Where to go next