#316 Aug 13, 2026

316. checked_duration_since — Your Deadline Passed, and Instant Subtraction Said Zero

This morning’s bite 315 said Instant has no Result to handle. True — but its subtraction hides a different trap: past a deadline, deadline - now is silently 0, and your retry loop spins forever with a zero timeout.

Since Rust 1.60, subtracting Instants in the wrong order doesn’t panic — it saturates to zero:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
use std::time::{Duration, Instant};

let earlier = Instant::now();
let later = earlier + Duration::from_secs(5);

// right order: the real gap
assert_eq!(later.duration_since(earlier), Duration::from_secs(5));

// wrong order: silently zero — no panic, no Err
assert_eq!(earlier.duration_since(later), Duration::ZERO);
assert_eq!(earlier - later, Duration::ZERO);

That’s kind to platform clock bugs, but cruel to deadline code. The classic pattern computes the remaining budget each iteration:

1
2
3
4
5
let remaining = deadline - Instant::now();
socket.set_read_timeout(Some(remaining)); // 💣 once the deadline
                                          // passes, remaining is 0
                                          // — which many APIs read
                                          // as "no timeout at all"

Zero never looks like “we’re done” — it looks like “no time elapsed”. Worse, set_read_timeout(Some(Duration::ZERO)) is an error on std sockets, and other APIs treat zero as infinite wait.

checked_duration_since tells the truth

None means the deadline is behind you — an unmissable, type-checked “time’s up”:

1
2
3
4
5
6
7
use std::time::Instant;

fn remaining(deadline: Instant) -> Option<std::time::Duration> {
    deadline.checked_duration_since(Instant::now())
}

// Some(budget) → keep waiting; None → deadline passed, bail
1
2
3
4
5
let earlier = Instant::now();
let later = earlier + Duration::from_secs(5);

assert_eq!(later.checked_duration_since(earlier), Some(Duration::from_secs(5)));
assert_eq!(earlier.checked_duration_since(later), None);

And when clamping to zero is what you want, say it explicitly with saturating_duration_since — same behavior as the bare subtraction, but the next reader knows it’s deliberate:

1
assert_eq!(earlier.saturating_duration_since(later), Duration::ZERO);

Same trio as integer arithmetic — bare op, checked_*, saturating_* — just on the clock. Pick the one that says what you mean.

← Previous 315. SystemTime::duration_since — NTP Set the Clock Back, and elapsed().unwrap() Panicked