-
Notifications
You must be signed in to change notification settings - Fork 533
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
Consider refactoring chrono to avoid systematic misuse of panics #815
Comments
Thanks for raising the issue @udoprog. I think it should definitely be considered whether we promote the My summary of things to consider:
|
Yes, one of things on my list for 0.5 is to audit all causes of panic in Chrono and remove it where possible. The cases that would be hard to prevent is things like Add or Sub impls that might overflow, where we have started making sure we offer checked variants as well. Any help you could offer on this topic would be much appreciated! |
Thanks for the positive reception! I'd be glad to put together a PR as a proposal now that I've gotten a feel for the waters. |
So I've got a work-in-progress branch where I tested some of the above things out, a preliminary review to see if it's on the right track would be nice before I do the work of making all doctests pass 😅. All the regular unit tests do pass. Note: this has been opened as #817 The broad strokes are: I've opted for a single I've opted to make loading the local timezone fallible instead of panicking (note that I have not yet changed the underlying implementations to be fallible). I think this is motivated because technically the underlying platform might perform checks which might fail all though what these look like exactly is a bit unclear. Making things fallible had the side effect of clearly marking the standard trait implementations which might panic, these are:
If you think something is going in the wrong direction I'd appreciate the guidance. |
Another alternative is to use the |
I think returning Result for these cases would be mostly more idiomatic than Option, we can guard implementations of the std Error trait as necessary. |
Like how much more reliable that makes code especially with regards to timezone changes overall. However it looks like for a few functions this is overkill and the ergonomics loss don't outweigh the benefits:
|
|
@djc We have 2 use cases in which we use
DateTime::from_utc(
// Date computation
.date_naive()
.and_hms_opt(0, 0, 0)
.unwrap(),
Utc,
) |
Hmm. I don't have any example where we're not using hms with either a constant or a data which we know by construction is always valid (such as values with modular arithmetic). That's not a big deal for us (only 4-5 use-cases throughout our codebase), but if you have a use-case that doesn't fall in either of these two categories I'm curious of what it looks like :) Thanks again! :) |
For testing fixed dates which are known valid (which I do as well, and some commenters on the PR such as #827 (comment)) maybe a macro that statically checks the date and cannot panic at runtime would be more suitable. let dt = datetime!(2006-01-02 15:04:05); |
Such a macro sounds good to me. I'm not sure how one would do this with declarative macros? |
This would be great, but I think we first need to be able to construct date(time)s in a const context (such that the decl or proc macro generated code can itself create the date), and then we need to consider if the MSRV bump for If only the former is possible we should still be able to have some non-panicking helper methods that can create dates at the start of given years or year+month combinations. Alternatively we could gate the It would be great to be able to store dates and times etc, in consts! |
TLDR: macro looks fine for our use case. |
Having re-read my comment, I'm not sure construction in a const context is actually necessary for this, but it is nice and solves some other issues as well. I've pushed a branch with a sample Line 33 in 9a0585a
This uses an unnamed const to force const evaluation and will give a compile error if the inputs are invalid, eg: let a = ymd!(2022, 11, 19); // compiles
let b = ymd!(2022, 11, 32); // compile error and in constants: const A: NaiveDate = NaiveDate::from_ymd_validated(2022, 11, 19); // compiles
const B: NaiveDate = NaiveDate::from_ymd_validated(2022, 11, 19); // compile error one disadvantage of This would raise the MSRV to 1.47, which just happens to fit nicely along with #712 (comment) and so perhaps 1.48 could be a good MSRV for edit: We could also use const generic parameters, for example: NaiveDate::from_ymd_validated_2::<2022, 1, 1>() |
Thank you! |
Wanted to share my experience on this: Of my uses of these (deprecated) functions that could be replaced by their *_opt variant, 71 lay in tests, and 1 lay in runtime code. However for the test cases, unwrapping (potentially once for ymd and once for time) is overkill. If there was an alternative designed for test cases (either promoting parsing from strings, or macros), I think that would then make complete sense to drop the non-opt variants. Additionally, it would be nice if the deprecation message included a link to this issue. |
@tyhdefu - thanks for the feedback, and it's a good idea to link to this issue in the deprecation feedback. Regarding your specific use case, perhaps the |
Yes this is fair enough. The let my_time = NaiveTime::<22, 05, 05>::new_hms(); |
I only have such cases as well |
If someone wants to add |
I've had a quick play with it. In addition to this, const implies that it will be checked at compile time, but infact it is the assignation to a const variable that does this check (or a macro that creates a const variable which we can create). To this end we could just have the *_opt functions be const, and unwrap them where we promise they will be available. I think this avoids confusion about exactly what const is. Having a const_ prefix like you suggest does not convey that it is not only but its const_panicky_at_runtime_ which is exactly what we are trying to avoid to by this refactor. People will think, oooh look const shiny and assume it cannot panic, using it instead of the _opt methods when actually it is equally fallible (and at runtime too). However i'm not sure how older rust versions handle the I've spend a little bit working on converting things to const and so far the problems I've encountered are:
|
The point is to offer a library with a conservative MSRV (that doesn't have the const features we need for const constructors) with opt-in support (for users who don't care about the conservative MSRV) leveraging const support. I agree the See also #882. |
As I see it, the main benefit is the macro, and also the majority of rules for (utc) date and time are fairly simple. Here is the macro in a gist: |
I think a macro in this case is just harder to use and maintain, especially when we have const compilation which already works for most users without requiring an extra abstraction (and the vast majority of users should be able to use it). |
- Creates a global Error enum - Breaks backwards compatiblility mainly because of promoting fallable functions (chronotope#263) - Some tests still fall - Not all doctests are fixed - to_naive_datetime_with_offset function is broken and needs fixing - serde related stuff is not checked properly This is a rebase of chronotope#817 onto the 0.5 main branch. Main differences: - Unify three different error structs - Removed ErrorKind - Adapted a lot of unit tests - Removed some commits from presumably unrelated branches (chronotope#829) or mainlined commits (chronotope#271) Co-authored-by: John-John Tedro <udoprog@tedro.se>
This commit adds the new constructor `from_timestamp_opt` to build a `DateTime<Utc>` from a UNIX timestamp. Figuring out how to convert a timestamp into a `DateTime<Utc>` was a common issue: - chronotope#88 - chronotope#200 - chronotope#832 This commit should make `DateTime<Utc>` creation more discoverable and intuitive. This commit respects the current convention of using the `_opt` suffix for fallible functions. As panicking variants on invalid input are deprecated, no panicking variant is provided. See [this issue](chronotope#815) for discussion about error handling and panics. Closes chronotope#832
This commit adds the new constructor `from_timestamp_opt` to build a `DateTime<Utc>` from a UNIX timestamp. Figuring out how to convert a timestamp into a `DateTime<Utc>` was a common issue: - chronotope#88 - chronotope#200 - chronotope#832 This commit should make `DateTime<Utc>` creation more discoverable and intuitive. This commit respects the current convention of using the `_opt` suffix for fallible functions. As panicking variants on invalid input are deprecated, no panicking variant is provided. See [this issue](chronotope#815) for discussion about error handling and panics. Closes chronotope#832
This commit adds the new constructor `from_timestamp_opt` to build a `DateTime<Utc>` from a UNIX timestamp. Figuring out how to convert a timestamp into a `DateTime<Utc>` was a common issue: - chronotope#88 - chronotope#200 - chronotope#832 This commit should make `DateTime<Utc>` creation more discoverable and intuitive. This commit respects the current convention of using the `_opt` suffix for fallible functions. As panicking variants on invalid input are deprecated, no panicking variant is provided. See [this issue](chronotope#815) for discussion about error handling and panics. Closes chronotope#832
This commit adds the new constructor `from_timestamp_opt` to build a `DateTime<Utc>` from a UNIX timestamp. Figuring out how to convert a timestamp into a `DateTime<Utc>` was a common issue: - chronotope#88 - chronotope#200 - chronotope#832 This commit should make `DateTime<Utc>` creation more discoverable and intuitive. This commit respects the current convention of using the `_opt` suffix for fallible functions. As panicking variants on invalid input are deprecated, no panicking variant is provided. See [this issue](chronotope#815) for discussion about error handling and panics. Closes chronotope#832
This commit adds the new constructor `from_timestamp_opt` to build a `DateTime<Utc>` from a UNIX timestamp. Figuring out how to convert a timestamp into a `DateTime<Utc>` was a common issue: - chronotope#88 - chronotope#200 - chronotope#832 This commit should make `DateTime<Utc>` creation more discoverable and intuitive. This commit respects the current convention of using the `_opt` suffix for fallible functions. As panicking variants on invalid input are deprecated, no panicking variant is provided. See [this issue](chronotope#815) for discussion about error handling and panics. Closes chronotope#832
This commit adds the new constructor `from_timestamp_opt` to build a `DateTime<Utc>` from a UNIX timestamp. Figuring out how to convert a timestamp into a `DateTime<Utc>` was a common issue: - chronotope#88 - chronotope#200 - chronotope#832 This commit should make `DateTime<Utc>` creation more discoverable and intuitive. This commit respects the current convention of using the `_opt` suffix for fallible functions. As panicking variants on invalid input are deprecated, no panicking variant is provided. See [this issue](chronotope#815) for discussion about error handling and panics. Closes chronotope#832
This commit adds the new constructor `from_timestamp_opt` to build a `DateTime<Utc>` from a UNIX timestamp. Figuring out how to convert a timestamp into a `DateTime<Utc>` was a common issue: - chronotope#88 - chronotope#200 - chronotope#832 This commit should make `DateTime<Utc>` creation more discoverable and intuitive. This commit respects the current convention of using the `_opt` suffix for fallible functions. As panicking variants on invalid input are deprecated, no panicking variant is provided. See [this issue](chronotope#815) for discussion about error handling and panics. Closes chronotope#832
This commit adds the new constructor `from_timestamp` to build a `DateTime<Utc>` from a UNIX timestamp. Figuring out how to convert a timestamp into a `DateTime<Utc>` was a common issue: - chronotope#88 - chronotope#200 - chronotope#832 This commit should make `DateTime<Utc>` creation more discoverable and intuitive. This commit respects the current convention of preferring fallible functions. It avoids however the `_opt` suffix as there is no panicking variant. See [this issue](chronotope#815) for discussion about error handling and panics. Closes chronotope#832
This commit adds the new constructor `from_timestamp` to build a `DateTime<Utc>` from a UNIX timestamp. Figuring out how to convert a timestamp into a `DateTime<Utc>` was a common issue: - chronotope#88 - chronotope#200 - chronotope#832 This commit should make `DateTime<Utc>` creation more discoverable and intuitive. This commit respects the current convention of preferring fallible functions. It avoids however the `_opt` suffix as there is no panicking variant. See [this issue](chronotope#815) for discussion about error handling and panics. Closes chronotope#832
This commit adds the new constructor `from_timestamp` to build a `DateTime<Utc>` from a UNIX timestamp. Figuring out how to convert a timestamp into a `DateTime<Utc>` was a common issue: - #88 - #200 - #832 This commit should make `DateTime<Utc>` creation more discoverable and intuitive. This commit respects the current convention of preferring fallible functions. It avoids however the `_opt` suffix as there is no panicking variant. See [this issue](#815) for discussion about error handling and panics. Closes #832
Closing in favor of #1049. We have non-panicking alternatives to almost all methods now, and making them default for 0.5 is definitely on the roadmap. We now have a good number of const initializers after #1043, #1080, #1205, #1286, #1337 and #1400. |
Many constructors in
chrono
has the behavior to panic on invalid inputs. This issue proposes to systematically change this behavior to instead make use of fallible constructors which in effect means the removal of the*_opt
family of functions.I'll be trying to make an argument for why below, at the end are the concrete changes I'm proposing.
Why should we do this?
The use of
chrono
has been the culprit of many unwanted panics in code that I've personally written in my career as a Rust programmer. The reason why this happens is sloppiness from my part. I use a method without realizing that it panics even though it says so in the documentation. But after consideration I've come to believe that the design of these methods could be improved.The standard I abide by when it comes to panicking is broadly that correct Rust programs should not panic. So what I'll try and determine next is if uses of these constructors contributes to writing correct Rust programs that do or do not panic.
There are a few tests I'll be applying here each time one of these methods are used:
Preconditions
If we look at the doc for most of these functions they contain wording such as
TimeZone::ymd
:This is a difficult precondition to check. An out-of-range date isn't a mechanical test such as "is this value smaller than 1000". It requires a deep understanding of the gregorian calendar system which
chrono
itself is responsible for providing. It's implausible that uses ensure that its input passes the required condition due to complexity. So the first test probably fails systematically across many projects.Runtime invariant inputs or tests
It's easy to find many uses of these functions where the input is runtime variant and not tests. I've found that many of these relate to ad-hoc use in combination with
serde
, which is a strong indication that its arguments truly are runtime variant. A bit of taxonomy lead me to believe that there might be a case of systematic misuse.What to do?
I believe many of the examples one can find of
chrono
production use fails the tests I proposed. The way to correct these uses would be through the*_opt
family of methods. But since the non-opt methods seems to be considered the default and probably leads to systematic problems when used I think it's worth considering changing them.What I'd suggest and would like to discuss is:
*_opt
sibling function but for one reason or another doesn't.*_opt
sibling function.Thank you!
The text was updated successfully, but these errors were encountered: