I often find that it would be useful to have a value for Instant which is some arbitrary point in time which is guaranteed to be in the past. On Linux (which I know most about), this should be doable by just creating Timespec::zero(), but I don't know enough about the other platforms, is there something about those that prevents this constant from existing?
One potential problem I can foresee is that because this is loosely defined (there are many possible values that all work), it might cause compatibility problems going forwards (where we might have a reason to change it from one value to a different value, but that might break existing code that is, e.g., using it as a sentinel), or where it's used as a sentinel on one platform and then an attempt is made to read it from a different platform where the value is different (although Instant is already platform-specific enough that such code would already likely be broken, so maybe that isn't an issue).
I assume that the reason why you want this rather than just using Instant::now() is so that it can be a compile-time constant? (Guaranteeing that an Instant is strictly in the past is difficult due to the possibility of a Rust program running during very early boot, before the timestamp has had a chance to change from its minimum value.)
Yes, that's why I want this over calling that function. It would also work for me if it was allowed to be the past or the present.
As for your concerns about people using it as a sentinel, I assume it would be fine so long as the docs come with a disclaimer that it's not to be used as a sentinel value, like NonNull::dangling() does. I haven't heard of any errors from someone mis-using that value as a sentinel.
I think it would make sense to use a different definition: Instant::MIN. This would be always the earliest representable instant, so there is no arbitrary choice. It would be useful in more situations, such as the start of a range of instants, or guards against numeric overflow.
It would have to be documented as platform-specific, and not guaranteed to remain the same on each platform, of course.
Not answering the main question, but by the way let old: Instant = unsafe { mem::zeroed() } does exist as a workaround.
It does not, do not ever do that. Instant makes no guarantees about its layout. What's the use case here?
The most common use-case I have is if I'm setting up a struct where, whenever a method gets called, it needs to do something if it hasn't recently enough. So I end up with something like this:
pub struct FooDoer {
last_bar_timestamp: Instant,
..
}
impl FooDoer {
pub fn do_foo(&mut self) {
if self.last_bar_timestamp.elapsed() >= Duration::from_secs(1) { self.do_bar(); }
..
}
fn do_bar(&mut self) {
self.last_bar_timestamp = Instant::now();
..
}
}
But then, when I go to construct a FooDoer, then I have a few options for how to handle constructing the timestamp:
- I can use
Instant::now(), which has a syscall and so it's impossible to do it at const-time (and maybe subtract the interval if I need it to happen on the first time). - I can use
Noneto accurately indicate that it hasn't happened yet, but then I have to rewrite everything to handle an option that will only be an option on the first time. - I can use something like the unsafe casting GP mentioned to construct an
Instantwhich I know is valid and in the past, but then I'm going against the stdlib public API and that's liable to be broken on an update.
And also, it often doesn't matter much when exactly the first call to do_bar() happens, so I could get around all of that if I just had a constant which is guaranteed to not be in the future, hence why I suggested just that as the guarantee.
Couldn't you store the time of the program startup into a OnceLock or LazyLock and use that?
Storing the program startup time still isn't const, and makes this logic wrong for the first second after startup:
Granted that logic is still iffy for Instant::MIN on devices which zero their timers to boot up (but that's probably fine).
you could have a next_bar_timestamp instead, initialized with Instant::now() + Duration::from_secs(1) and then you check if next_bar_timestamp <= Instant::now()
This is not a niche thing -- CLOCK_MONOTONIC is counted from boot time on Linux and probably many others. But for the 1 second delay in this example, I agree it's probably fine.
Another quirk of MIN is that this could be far in the past, e.g. on Unix targets that use tv_sec: i64, versus Windows with unsigned Instant(Duration). So the range of Instant::MIN.elapsed() could be wildly different!
I often have issues in examples and test, with postgres not allowing to write chrono datetimeTZ min(), because it is too small. The fix is to use unix epoch zero instead.
So I agree, minimum values is too early. Related: Gregoran calendar issues
Big Bang issues even -- i64::MIN seconds would be over 292 billion years ago!
Swift calls these constants DISTANT_PAST and DISTANT_FUTURE.
Can't you start the timestamp with Instant::now() - Duration::from_secs(1)?
No, because the use case was having the value available in const. Moreover, that subtraction may panic.