According to this, one can expect that maximum possible duration value is at least i64::MAX seconds. But in fact, the full range that i64 seconds gives us is restricted to i64::MAX milliseconds (see #16626 for more details).
I propose to change the internal representation of Duration to
pub struct Duraion {
ticks: i64, // A single tick represents 100 nanoseconds
nanos: i8 // to hold values from -99 to 99
}
Some pros:
Internals of struct Duraion are not exposed. So, we are free to change them.
New representation is 3 bytes smaller.
Methods like num_milliseconds etc will return correct i64 values, see #16626.
TimeSpan in .NET and Duration in Joda-Time types do the same.
Some cons:
The overall range of values for Duration will be shorter:
i64::MAX seconds gives us 106751991167300 days. We don’t use need it.
i64::MAX milliseconds gives us 106751991167 days. This is our current choice.
i64::MAX ticks gives us 10675199 days. The range is still long enough, who cares?
Then I gonna do it and make a pull request. Right? Or wait for a while?
I am a newbee here and don’t know if this is the right way to to contribute to Rust.
I think internal representation should reflect source data as much as possible so as to not introduce unnecessary transformations. The second property of a good internal representation is low bit waste. It may seem trivial in an isolated case but imagine large vectors of these and the possibly wasted cache space.
These two goals often collide. If so, go for fewer transformations. You can then transform to whatever representation suits your application’s needs.
Also, don’t mix API and implementation. It’s better to provide interface functions that transform the data to your preferred form than to have leaky abstractions.
I also don’t like the second property (nanos) because I don’t think that we need a nanosecond precision duration. Without nanos duration type behaves like i64. That means it transforms into code that runs faster etc.
TimeSpan (.NET) and Duration (Joda-Time) both don’t need nanos. But according to what i see - we do!
I don’t have a strong opinion about this, but I will say that being 3
bytes smaller isn’t much of a win due to the alignment rules, which
will wind up rounding the size to the same thing either way.
3 bytes more or 3 bytes less … Remember that we deal with a dead simple type like duration. It’s not very useful by itself, but it will be the base for more complex types. The more efficient it’ll be, the better.
And the best choice we could make, IMHO, is to define duration type like that:
pub struct Duraion {
ticks: i64, // A single tick represents 100 nanoseconds
}
I’ll fit to alignment rules and also give us a bare metal performance. But, there won’t be nanos. Others don’t need them, I don’t understand why we do!
Nanosecond precision might be most useful for reasonably short durations. We could encode very long durations (at least 2^62 ns) with lower precision, in microseconds or milliseconds.
I don’t care much about the three wasted bytes as long as I can use other functions to acquire just the milliseconds or nanos when the full range isn’t important. Usually one needs one one of the two.
For completeness, I think it’s awesome to have a standard API with a data type that can represent this big range in precision.
I found one more reason why ticks: i64, nanos: i8 (or even ticks: i64) is less appropriate: num_microseconds method will always return a valid i64 value. I mean we should change method signature from:
pub fn num_microseconds(&self) -> Option<i64>
to
pub fn num_microseconds(&self) -> i64
But it might to be a compatibility issue. Or leave it as is, then it’ll be inconsistent.
Just an idea. Is there any reason we wouldn’t want a variable-precision Duration class, like C++'s std::chrono::duration? The equivalent of std::ratio can be done with a trait and static methods:
struct Duration<T: Signed+Bounded, P: Ratio> {
ticks: T
}
where T is the type the user wants to use and P the period that spans each tick.
T would probably be i64 in most cases, but smaller-sized integers and floats have valid use cases, I think.
The tricky part would be implementing with_period<N: Ratio>(&self) -> Duration<T, N>. A cast function alike to C++'s duration_cast to cast between duration types, that would have to deal the type conversion and precision issues.
It’s a big change, but I think I should propose this before 1.0.
Fast edit: Now that I think of it, wouldn’t calling to P::num() require UFCS to be implemented first? We could work around it by using a zero-sized field with an instance of the type and having Ratio take a &self parameter, but I don’t like having to hack around the language, even less for the std…