#145 May 17, 2026

145. Duration::from_nanos_u128 — Round-Trip Nanoseconds Without the u64 Cast

Duration::as_nanos() hands you a u128. Duration::from_nanos() takes a u64. You feed one into the other and the compiler yells at you — or worse, you cast and quietly truncate at 584 years. Rust 1.93 closed the loop with from_nanos_u128.

The mismatched-types papercut

The old API was asymmetric. Going from Duration to nanos was 128-bit:

1
2
3
4
5
use std::time::Duration;

let d = Duration::new(7, 250);
let n: u128 = d.as_nanos();
assert_eq!(n, 7_000_000_250);

Coming back, though, you only got from_nanos(_: u64) — so the round-trip needed a cast:

1
2
3
4
5
use std::time::Duration;

let n: u128 = Duration::new(7, 250).as_nanos();
let back = Duration::from_nanos(n as u64); // narrowing cast, fingers crossed
assert_eq!(back, Duration::new(7, 250));

That as u64 silently truncates anything past u64::MAX — and u64::MAX nanoseconds is roughly 584 years. Inside a calendar app you’ll never notice. Inside a scientific or simulation context, you absolutely will.

from_nanos_u128 matches as_nanos

Rust 1.93 stabilised Duration::from_nanos_u128, a const fn that takes the full 128-bit value:

1
2
3
4
5
use std::time::Duration;

let n: u128 = Duration::new(7, 250).as_nanos();
let back = Duration::from_nanos_u128(n);
assert_eq!(back, Duration::new(7, 250));

Same shape on both sides. No cast, no truncation, no silent wraparound.

Past the 584-year ceiling

Where the new constructor actually earns its keep is when you have nanoseconds counts that wouldn’t fit in a u64:

1
2
3
4
5
6
7
8
9
use std::time::Duration;

// 10^24 ns is ~31.7 million years — well past u64::MAX nanos
let nanos: u128 = 10_u128.pow(24) + 321;
let d = Duration::from_nanos_u128(nanos);

assert_eq!(d.as_secs(), 10_u64.pow(15));
assert_eq!(d.subsec_nanos(), 321);
assert_eq!(d.as_nanos(), nanos); // exact round-trip

Duration itself stores (u64 seconds, u32 nanos), so it has plenty of room — the old from_nanos was just bottlenecked by its argument type.

One thing to watch

from_nanos_u128 panics if you hand it more than Duration::MAX worth of nanoseconds. If you’re pulling values from user input or untrusted sources, guard the upper bound yourself — there isn’t a checked_from_nanos_u128 (yet).

When to reach for it

Use from_nanos_u128 whenever you already have a u128 of nanoseconds — typically because it came out of as_nanos, an arithmetic accumulator, or a high-precision external clock. Stick with the plain from_nanos(_: u64) for short-lived timeouts and durations measured in milliseconds or seconds; the u64 is plenty.

Stabilised in Rust 1.93 (January 2026). Available as const fn, so it works in const contexts too.

← Previous 144. Vec::into_raw_parts — Hand a Vec to C Without the ManuallyDrop Dance Next → 143. Vec::dedup_by_key — Collapse Consecutive Duplicates by a Derived Key