Mehdi Akiki
Rust Failure Atlas / Runtime, memory, and library APIs

RFA-679 · Case file with fixtures · Case 651 of 694 · Runtime evidence

Instant::duration_since Saturates When Operands Are Reversed

duration_since means self minus earlier and saturates at zero for reversed order. Use checked_duration_since when inversion must remain observable.

Reviewed
Rust
Rust 1.98.1, edition 2024
Targets
all Rust targets
Profiles
dev, release, test

Direct answer

What this Rust failure means

Why it happens
duration_since is directed self-minus-argument subtraction and its saturating behaviour hides reversed order as zero.
First discriminating check
Name start and end roles, put the later instant in self position, and use checked_duration_since when inversion must remain observable.

end.duration_since(start) means end - start. On current Rust, the method saturates to zero when start is later than end. The failing fixture reverses two instants one second apart and receives zero, not an absolute one-second difference.

Since has a direction

The Instant::duration_since documentation defines a directed elapsed span from the argument to self. It is not absolute distance between two points.

I name variables started_at and finished_at and write finished_at.duration_since(started_at). Names such as a and b make reversal easy, especially in comparison helpers.

The repaired fixture asserts correct order and also calls checked_duration_since in the reversed direction.

Saturation can hide bad event ordering

Zero is convenient for some timing code and avoids a panic when platform monotonicity has unusual behaviour. But zero can mean equal instants or reversed instants. If inversion indicates a stale timestamp, mixed clock source, or incorrectly ordered event, saturation loses useful evidence.

checked_duration_since returns None when self is earlier than the argument. I use it at boundaries where order is part of data validity.

If zero is the correct floor for remaining-time logic, the saturating method is appropriate. The decision depends on whether inversion is expected domain input or a broken invariant.

Instant is process-local monotonic time

Instant is for measuring elapsed time, not storing civil timestamps or exchanging time across machines. Its representation and reference point are platform-specific. I do not serialize it as a durable event time.

For logs and protocols, SystemTime or an explicit timestamp format carries wall-clock meaning, with its own possibility of adjustment. A system may store both: wall time for correlation and Instant for local duration.

Comparing values created in different process lifetimes or fabricating them from raw external numbers has no sound portable meaning.

elapsed is also directional

Instant::elapsed is essentially current instant duration since the stored instant. It assumes the receiver is not in the future. A stored future deadline is not an elapsed-start value.

For a deadline, compare current time with the deadline and compute checked remaining duration in the appropriate direction. Naming helpers elapsed_since_start and remaining_until_deadline prevents one method from serving two opposing concepts.

Adding very large durations to an Instant can overflow the platform range. Checked addition is preferable when durations are external or unbounded.

Distributed event order needs more than clocks

Even accurate wall clocks do not provide causal order across services. Network delay and adjustment can produce timestamps that appear reversed. Instant cannot cross that boundary.

I use request IDs, sequence numbers, logical clocks, or protocol acknowledgements when causal ordering matters. Local monotonic measurements then quantify time within one process.

This avoids treating saturation as a repair for distributed ordering. Returning zero may keep a metric valid, but it does not prove which event happened first.

Tests should avoid sleeping

The fixture creates a later Instant by adding a known Duration, so it is deterministic and fast. Tests based on short sleeps can be flaky under scheduler load and cannot precisely assert elapsed bounds.

For timeout logic, I prefer an injectable clock or controlled time facility. Tests cover equal, forward, reversed, and overflow-adjacent cases according to supported operations.

Metrics deserve the same care. Recording saturated zero for every reversed sample can make a latency dashboard look better exactly when instrumentation is broken. I increment a separate invalid-order counter or drop the sample with a diagnostic. Data-quality failures should not silently become excellent performance measurements.

My Instant checklist

  • Which instant is start and which is end?
  • Does zero legitimately include reversed order, or should inversion be an error?
  • Is checked_duration_since needed to preserve evidence?
  • Am I confusing elapsed time with time remaining to a deadline?
  • Is Instant being serialized or compared across process boundaries?
  • Does causal order require sequence information beyond clocks?
  • Can external durations overflow Instant arithmetic?
  • Are tests deterministic without scheduler sleeps?

The core principle is that time differences have direction. duration_since deliberately floors a reversed difference at zero, while the checked form preserves inversion. I select between them based on whether ordering failure is acceptable or diagnostic information.