Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Time units #52556

Closed
wants to merge 12 commits into from
Closed

Time units #52556

wants to merge 12 commits into from

Conversation

newpavlov
Copy link
Contributor

@newpavlov newpavlov commented Jul 20, 2018

This PR adds NANOSECOND, MICROSECOND, MILLISECOND, SECOND constants to time module for both std and core. Instead of sleep(Duration::from_millis(10)) it will allow us to write:

use std::time::MILLISECOND as MS;

sleep(10*MS);

As was discussed in #51610 adding constants for common durations will make creation of Duration significantly more ergonomic. This PR is a draft which can be changed upon discussion. Small explanation of the choice made in this PR:

  • Why NANOSECOND and not NS? While I think the latter is usually more ergonomic, abbreviation confuses error messages and can be ambiguous in some situations. Full names can be shortened via aliasing as was shown in the example earlier. Additionally this naming will be similar to the other languages (in particular to Go and C++).
  • Why not use a separate module time::units? I think that a separate module will be redundant, currently time module has only one constant UNIX_EPOCH, thus they will not be lost in the documentation, and it will help with teaching existence of those constants.
  • Why implement Mul<Duration> for u32? This will allow us to write thread::sleep(10*M + 20*S) instead of less pleasant thread::sleep(M*10 + S*20). Ideally I would like to have a way to describe commutative property of multiplication in this case to the type system, but oh well...
  • Why no MINUTE, HOUR and DAY? While as units accepted for use with the SI they are strictly defined in number of seconds, there is caveats to those units if UTC is considered, e.g. hour can be 3,599–3,601 seconds long, depending on conditions. Thus to avoid confusion some argue its probably better to be conservative and not include them. (see Add std::time::Duration::{from_days, from_hours, from_mins} #47097 for more context) Though we probably could use prefixes like SI_ or STD_ for those units to highlight the difference from UTC units. UPD: they are included back. Final decision can be made on feature stabilization.
  • No RFC? I think this change is too small for a separate RFC. Although it could be useful to link this PR on reddit and internals forum.

UPD: PR was updated based on replies.
UPD2: It looks like the prevalent opinion is that custom suffixes will be a better solution, i.e. an ability to write something like this:

use std::time::literals::{s, ms};
sleep(1s + 1ms);

So even if this feature will get merged, it will probably will not be stabilized and removed at some point.

@rust-highfive
Copy link
Collaborator

r? @Kimundi

(rust_highfive has picked a reviewer for you, use r? to override)

@rust-highfive rust-highfive added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Jul 20, 2018
@rust-highfive

This comment has been minimized.

@newpavlov
Copy link
Contributor Author

Hm, what will be the best approach to solve the issue with #[unstable(..)] attribute?

@@ -501,6 +524,15 @@ impl Mul<u32> for Duration {
}
}

#[unstable(feature = "time_units", issue = "0")]
impl Mul<Duration> for u32 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This impl is going to be insta-stable unfortunately.

Copy link
Contributor Author

@newpavlov newpavlov Jul 20, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should I keep the attribute or use instead something like #[stable(feature = "time_units", since = "1.29.0")]?

Copy link
Member

@kennytm kennytm Jul 20, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@newpavlov The latter (with a different feature name, otherwise tidy will reject it).

Copy link
Contributor Author

@newpavlov newpavlov Jul 20, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, what should I use for feature then? With time_units it can not be built.

UPD: Ah, haven't noticed your update. :)

const H: Duration = Duration::from_secs(60*60);
/// 1 day `Duration`
#[unstable(feature = "time_units", issue = "0")]
const D: Duration = Duration::from_secs(24*60*60);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to #47097, I doubt we want to support hours and days.

Abbreviating minutes to M is also confusing (months? meters?). The SI standard short form MIN conflicts in meaning with "minimum". I would like to leave this out as well, keeping only S, MS, US and NS.

@@ -30,6 +30,7 @@ use sys_common::FromInner;

#[stable(feature = "time", since = "1.3.0")]
pub use core::time::Duration;
pub use core::time::{NS, US, MS, S, M, H, D};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line needs an #[unstable] attribute.

@kennytm kennytm added T-libs-api Relevant to the library API team, which will review and decide on the PR/issue. relnotes Marks issues that should be documented in the release notes of the next release. labels Jul 20, 2018
@scottmcm
Copy link
Member

Prior art: C++ spells the types out in full, https://en.cppreference.com/w/cpp/header/chrono#Convenience_Typedefs, though the literal suffixes abbreviate.

The consts are a bit awkward since they encourage things like d+S, and the casing convention is unfortunate, as S is siemens and H is henrys, and M is 106.

The types in C++ are often used as functions; what if these became function instead? The reading order is admittedly backwards, but sleep(hours(1)); feels pretty good to me.

@rust-highfive

This comment has been minimized.

@newpavlov
Copy link
Contributor Author

newpavlov commented Jul 20, 2018

For now I've removed minute, hour and day. Personally I don't have a strong need for them, so I guess it could be added later if someone will push for it. Although I'll note that #47097 the main issue was with dasy, not with hours and minutes, plus Go has hour and minutes as well (but not days).

@scottmcm
I think no one will think about S in sleep(10*S) as of siemens. Plus as was noted you always can use time::S. I think that sleep(10*M + 20*S) is a better option than sleep(minutes(10) + seconds(20)). An alternative solution (and probably the most ergonomic) will be to use (imported) custom literals, so we could write sleep(10m + 20s), but I don't know about proposal which have an implementation chance in the near future.

BTW if Duration::new will become const fn we could probably consider deprecation of Duration::from_* methods.

@rust-highfive

This comment has been minimized.

@rust-highfive

This comment has been minimized.

@rust-highfive

This comment has been minimized.

@rust-highfive

This comment has been minimized.

@rust-highfive

This comment has been minimized.

@varkor
Copy link
Member

varkor commented Jul 20, 2018

I think spelling out unit names would be a better idea (I don't have a strong opinion about functions vs constants), for the two reasons @scottmcm states (case sensitivity and name collisions) as well as the awkwardness of non-ASCII symbols (for microseconds, for instance). If we just pick the ASCII character that visually looks most similar to a particular non-ASCII character, it's going to exacerbate the other issues and can't necessarily be immediately guessed by a user, which isn't a problem if the full name is used.

You can always alias when you import.

@newpavlov
Copy link
Contributor Author

newpavlov commented Jul 20, 2018

@varkor
I've updated the PR with renaming. In addition to your arguments S apparently confuses some error messages.

@kennytm
How about using SI_HOUR and SI_DAY? And maybe rename MINUTE to SI_MINUTE as well? They are strictly defined in number seconds without any leap seconds, thus this naming should solve the caveats issue.

@kennytm
Copy link
Member

kennytm commented Jul 20, 2018

@newpavlov Those SI_ prefix gives it feel of C-style API 😛

IMO 620*S is not really worse than 10*M + 20*S, so I still prefer to not include units above seconds.

@kennytm
Copy link
Member

kennytm commented Jul 20, 2018

Using u as a substitute of µ is pretty standard in programming (e.g. usleep, see also https://en.wikipedia.org/wiki/Micro-#Symbol_encoding_in_character_sets), although the name US looks funny (🇺🇸).

I'm OK with either abbreviation or spelling out:

  1. S, MS, US, NS, or
  2. SECS, MILLIS, MICROS, NANOS, or
  3. SECONDS, MILLISECONDS, MICROSECONDS, NANOSECONDS

@newpavlov
Copy link
Contributor Author

And what about 93600*S instead of D + 2*H? :P Personally I don't see much reason not to include hour and minute (and day by extension, as it is accepted for use with the SI), especially considering that they are present in other languages. For now I've removed them, lets hear other opinions.

As for SECS, MILLIS and etc., I don't think it will be a good choice, as they will be read as plurals, which they are not. It works for from_* methods because it's implied you will usually call those methods not with 1.

@matthieu-m
Copy link

@scottmcm:

The types in C++ are often used as functions; what if these became function instead? The reading order is admittedly backwards, but sleep(hours(1)); feels pretty good to me.

@newpavlov

As I wrote on internals forum, ideal solution for me will be custom literal suffixes à la C++, so we could write code like this:

It would be possible to create a trait SiTimeUnits, implement it for u32, and get:

3.d() + 6.h() + 19.s() + 273.us();

Slightly more verbose (extraneous parentheses), but pretty idiomatic: traits have been used to provide method extensions in Rust forever (SliceExt?).

@newpavlov
Copy link
Contributor Author

@matthieu-m
I think that constants are much easier to find and explain. And instead of such ad-hoc trait IMO we better push for custom literals functionality.

@scottmcm
Copy link
Member

a trait SiTimeUnits

I quite like that, actually. Especially because then you import the one trait and it doesn't pollute the namespace the way that importing a bunch of constants does. And fixes the casing issue.

@HeroicKatora
Copy link
Contributor

@matthieu-m I'm not sure about long or short form. I slightly favor the long form similar to the current interfaces (secs, millis, micros, nanos, etc.). In no case would I like a trait with cryptic names such as s in the prelude, but I would be okay with it being an usable trait for those favoring the established definitions of SI units.

@newpavlov In my opinion, traits and being able to introduce new methods provide superior functionality and consistency compared to custom literals. In particular, the tradeoff of complicating the language when we could solve this with existing features does not sound worth here. It is also a little unclear to me what that would specifically accomplish here. Custom literals should give you power to reinterpret the literal in the code, it feels wrong to also use them if one simply wants to convert a conventionally parsed primitive into another type. Keep in mind, C++ has to resort to this style only because it does not have methods on primitive types.

@flexera-cnixon
Copy link

It seems there are enough opinions on this PR that it could make sense for this to be an RFC?

The embedded-hal ecosystem is also thinking about things in this area rust-embedded/embedded-hal#59

@newpavlov
Copy link
Contributor Author

@HeroicKatora
There is also a cost with "noisiness" of the language. (and Rust already has reputation of being noisy) And I want to see custom literals regardless of why C++ initially introduced them. I think it's a cool feature, learning which will be an improvement for Rust. Custom literals and extending primitives methods will look and feel quite differently. Compare:

sleep(1s + 100ms)
sleep(1.s() + 100.ms())

The latter variant is more noisy, more surprising for beginners (what? is it a method on primitive integer?), more annoying to write and read, while the former takes minimum amount of characters and explicitly shows that you are not dealing with primitive integers, but with something else. Also if we take other units into consideration (which can be implemented by third-party crates, see e.g. embedded-hal link earlier) I think seeing and reading 1m/5s in the code will be more pleasant than 1.m()/5.s().

@HeroicKatora
Copy link
Contributor

@newpavlov You mean noise as in there are three extra characters per suffix? I think I see where you are getting at. But the alternative is additional complexity in language, syntax and all the way down to the grammar. And I'd argue that the parts which you qualified as noise actually help process the code mentally in many cases, especially since it does not necessitate teaching and learning a new kind of rule but simply generalizes being able to call methods on values.

In any case, it now appears that the original proposal does not seem like the best solution to either of us. So I would follow @flexera-cnixon by opening an RFC. I would also extend on my comment above about advantages and disadvantages for literal suffixes there.

@newpavlov
Copy link
Contributor Author

I've published pre-RFC for custom literals on internals forum. Let's continue discussion about them vs trait based approach there.

@scottmcm
Copy link
Member

I agree with @flexera-cnixon; it seems there's enough of a design space here that this PR should be closed in favour of a discussion on IRLO and/or a full RFC on that repo.

@WiSaGaN
Copy link
Contributor

WiSaGaN commented Jul 23, 2018

For constants, however, I think it could be very beneficial for these to be associated constants of Duration instead of module constants of time. It adds more context so you know exactly what type it is. Compare time::SECOND versus Duration::SECOND.

I agree with this. We recently stabilized UNIX_EPOCH as associative const of SystemTime. We probably should do the same by adding these units as associative consts of Duration.

@newpavlov
Copy link
Contributor Author

newpavlov commented Jul 24, 2018

@scottmcm
I agree that ideal solution for this problem is custom literal suffixes, but probably we will not get them in a mid-term. Can't this PR be merged as a stop-gap Nightly-only solution until custom suffixes will be introduced?

@WiSaGaN
Note, that currently you can't write use std::time::Duration::SECOND as S;, so you'll have to write sleep(2*Duration::SECOND) instead of just sleep(2*S) which is obviously unergonomic. And BTW I do not agree with the motivation for associated UNIX_EPOCH which was "it's not very discoverable as a separate const in the module". For me personally it probably would've been harder to find. Though it indeed improves use of UNIX_EPOCH a bit, as you don't have to use a separate import for it any more.

@scottmcm
Copy link
Member

merged as a stop-gap Nightly-only solution

Why would this be valuable over putting things in a crate? This feature needs no special access to duration internals, nor does it need internal-only features (like intrinsics or similar), so why not just do use duration_consts::S; instead of use std::time::consts::S;, and use it on stable?

(This isn't a method on a prelude trait, so there's no future-conflict hazard with a crate import.)

@newpavlov
Copy link
Contributor Author

You are right, I guess it can be in a separate crate. But convenience impl Mul<Duration> for u32 must be in the std. Should I create a separate PR for it?

@scottmcm
Copy link
Member

scottmcm commented Jul 24, 2018

@newpavlov Scalar multiplication of durations definitely sounds useful and separable. At least for myself, I've certainly wanted things like 2 * d before, And since trait impls are insta-stable so must be FCP'd, it's probably worth splitting out regardless. (Maybe include scalar division too? Like d += d/2;...)

Oh, impl Mul<u32> for Duration (and Div) already exist. Then I think the symmetric impl is a bug fix.

@newpavlov
Copy link
Contributor Author

Maybe include scalar division too?

I am not sure if I understood you, we already have impl Div<u32> for Duration and result of 2/duration can not have Duration type.

@WiSaGaN
Copy link
Contributor

WiSaGaN commented Jul 25, 2018

@newpavlov

Note, that currently you can't write use std::time::Duration::SECOND as S;

Is this something not yet implemented or something we shouldn't do? If it is the former, then should we fix that instead?

For me personally it probably would've been harder to find.

UNIX_EPOCH is actually quite unique to SystemTime, so it does not make as much of an impact to the crowdness of std::time. However, we may have many constants like SECOND, HOUR, DAY. Putting them all under std::time can quickly make the namespace messy.

@scottmcm
Copy link
Member

should we fix that instead?

FWIW, I'd be in support of an RFC to allow useing associated things. I've often thought use Default::default; would be nice, and I like use Duration::SECOND as S; better than forcing S on everyone, without being materially longer.

@kennytm
Copy link
Member

kennytm commented Jul 25, 2018

If namespace pollution is the only problem we could introduce std::time::consts::{NANOSECOND, ...} just like std::f32::consts::*.

@WiSaGaN
Copy link
Contributor

WiSaGaN commented Jul 25, 2018

@kennytm

If namespace pollution is the only problem we could introduce std::time::consts::{NANOSECOND, ...} just like std::f32::consts::*.

This was actually discussed in #24915 (comment) where a deprecation of std::f32 module was mentioned, but I am not sure what is the current situation. Maybe we would not have introduced a module or consts if we had associated consts at the time?
Specifically std::f32::consts to me seems more about mathematical constants not const as a language concept.

Overall, I feel it would be beneficial to discuss the general treatment of introducing consts in std as this probably wouldn't be the only time we want to add commonly used consts of a specific type. Some of the questions may be:

  • What should be the general criterion to introduce them in std (comparing to an external crate, etc.)?
  • Where should we put it?
  • Is it worth it to introduce additional language level change to these library types?

@newpavlov
Copy link
Contributor Author

BTW what do you think about adding division and multiplication by f32 (or f64)? It has a slight risk of small imprecision, due to how floats work, but I think it will an ergonomic improvement to be able to write 1.25*SECOND.

@newpavlov
Copy link
Contributor Author

I am closing this PR and will create a separate one which will contain only additional impls.

Also I've published durations crate, it will be less pleasant to use than std constants, but I guess it's better than nothing.

@newpavlov newpavlov closed this Jul 28, 2018
@newpavlov newpavlov deleted the time_units branch July 28, 2018 23:46
kennytm added a commit to kennytm/rust that referenced this pull request Sep 20, 2018
…r=alexcrichton

Duration div mul extras

Successor of rust-lang#52556.

This PR adds the following `impl`s:
- `impl Mul<Duration> for u32` (to allow `10*SECOND` in addition to `SECOND*10`)
- `impl Mul<f64> for Duration` (to allow `2.5*SECOND` vs `2*SECOND + 500*MILLISECOND`)
- `impl Mul<Duration> for f64`
- `impl MulAssign<f64> for Duration`
- `impl Div<f64> for Duration`
- `impl DivAssign<f64> for Duration`
- `impl Div<Duration> for Duration` (`Output = f64`, can be useful e.g. for `duration/MINUTE`)

`f64` is chosen over `f32` to minimize rounding errors. (52 bits fraction precision vs `Duration`'s ~94 bit)
@ghost ghost mentioned this pull request Jan 6, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
relnotes Marks issues that should be documented in the release notes of the next release. S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet