Tiny feature proposal: Duration::try_new(secs, nanos)

Duration::new(secs, nanos) can panic, because if its nanos argument is greater than 999,999,999, it propagates the excess into the seconds field, and that addition can overflow. Thus for example

use std::time::Duration;

fn main() {
    println!("{:?}", Duration::new(u64::MAX, 1_000_000_000_u32));
}

will panic (playground demo).

Can we add an alternative version, probably named try_new, that returns an Err value in case of overflow, instead? I want this for parsing textual representations of timestamps: I'm already returning Result<Duration, MyTimestampParseError> for a bunch of other failure cases, including overflow detected during decimal-to-binary conversion, so it seems inconsistent for this one overflow case to panic. (Also, if I wrote the parser correctly, the panic is unreachable, but the compiler can't prove it.)

Checked Duration constructors exist for conversion from f32 and f64 (try_from_secs_f32 and ..._f64), but f64 does not have enough bits to represent present-day times with nanosecond precision[1], so these are inherently lossy for my use case.


  1. f64 has 53 bits of mantissa, therefore it can only represent an offset from the Unix epoch with nanosecond precision if that offset is within ±2**53 nanoseconds of 1970-01-01T00:00:00.000000000Z. That range translates to 6pm on September 18, 1969 until 6am on April 15, 1970. ↩︎

3 Likes

In the meantime, you could probably use Duration::from_secs(secs).checked_add(Durarion::from_nanos(nanos))

3 Likes

I think he needs to validate that number of nanoseconds falls into desired range.

@zackw

This feature is small enough, so I would simply open a PR in the Rust repo.

I suppose by “desired range” you mean that nanos < 1_000_000_000? Because I don’t interpret it that way. The statement was

which referred to “overflow” as described in

So doing a checked_add of two Duration should do exactly the right thing here (and I cannot imagine that approach being improperly optimized, either; though I did not check the code it generates).

1 Like
Duration::from_secs(10).checked_add(Duration::from_nanos(3_000_000_000))

will happily return 13 seconds duration. In my understanding, the OP wants to get an error in such cases.

It doesn’t, but 10_000_000_000 overflows u32. Duration::new(10, u32::MAX) works fine though.

Yes, you are right, just checked it in playground. Though, it's somewhat surprising for my taste.

I suppose, all we can do is speculate. @zackw feel free to clarify :wink:

That being said, I’d think it might be a bit weird for a new method not to panic in all cases where a try_new method errors.

To clarify, what I want is indeed exactly shorthand for Duration::from_secs(secs).checked_add(Duration::from_nanos(nanos as u64)). Thanks for the suggestion, I don't think I would have thought to try that. (I did check the code generation; Duration::from_secs and Duration::from_nanos both call Duration::new, but the compiler can prove that those calls supply arguments that cannot lead to the panic.)

It's fine for my use case that Duration::from_secs(10).checked_add(Duration::from_nanos(3_000_000_000)) == Duration::new(13, 0). I just want to get rid of the panic on Duration::new(u64::MAX, 1_000_000_000), mainly so that I can statically verify that the larger program never panics.

(My parser already prevents the nanos argument from going outside the subsecond range, i.e. the compiler could in principle prove nanos <= 999_999_999_u32 if it was clever enough. But it isn't.)

1 Like

In cases where the compiler fails to prove something you know to be true, you could add an assert!. In addition to checking the condition, that will also cause the compiler to assume it to hold after running the assert, which can improve optimization.

2 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.