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

failure -> thiserror/anyhow #220

Merged
merged 10 commits into from
Jan 20, 2023
Merged

failure -> thiserror/anyhow #220

merged 10 commits into from
Jan 20, 2023

Conversation

cjordan
Copy link
Contributor

@cjordan cjordan commented Nov 28, 2022

... and a bunch of other small changes.

The commits themselves are well commented in terms of their purposes, but basically this work removes error handling with failure, instead using thiserror/anyhow, which is in widespread use in the Rust community.

It seems GitHub hasn't recognised that this is the same PR I submitted about 18 months ago; here's @pkgw's comment from that time: #148 (comment)

I'm not sure I understand all the questions from the comment made on the previous PR, but I'll attempt to explain my thinking here. Using enums instead of structs as error types means that functions can return a "family" of errors rather than one specific error. It is possible to have a error struct contain a String and just pass that around, with the string being a human-readable message, and there's nothing necessarily wrong with this (indeed I think there's no single right way to do errors). IMO it is better to have enums because then the errors can be typed, and if the caller wants to, the error variants can be matched and handled. I guess this is also possible with the string approach, but this is much looser than using enum variants.

FWIW I leverage the typed error approach heavily here.

In most cases I expect that users don't have the time/effort to do this kind of fancy error handling, and because most errors are unrecoverable (e.g. your code wants to open MS table "DATA", but it isn't there, what else could you possibly do?), the ? should work as one expects it to.

All this said, I have been lazy in my porting of the miriad code, where I just dump existing string errors into a catchall Generic error enum variant; this could obviously be expanded depending on how much you/others care.

In terms of extensibility; I think semver compatibility is something every library maintainer should consider high priority, regardless of how errors are handled, so adding new variants to an existing error enum should be considered as a breaking change, requiring a new release of a crate. If the "stringy" approach were employed instead, obviously more errors could be stuffed into a catchall variant, but I'd be careful about pulling the rug out from users attempting to handle error variants, even if they're not typed.

Finally, I'm not sure if anyhow supported backtraces at the time of my last PR, but it appears to do so now. I haven't tested them or even used them, but the code compiles, so 🤷

If/when this PR is merged, it would be great to have a new release of rubbl and it's family of crates. Thanks once again for your time!

This commit migrates error handling with failure to thiserror and/or
anyhow. This is mostly good because (1) the wider Rust community uses
thiserror/anyhow for error handling, (2) failure has been
deprecated, (3) GitHub occasionally sends scary emails that there are
security risks associated with using failure.

I've tried to be thorough in the conversion, but ultimately how things
should look is quite opinionated. Internal tests pass, and my own code
works with the updated casatables code. A lot of things are drop-in
replacements, other things needed a bit more work, like setting up new
error types.

This commit also removes the ctry! macro. The concept provided by this
macro is pretty much the same as using anyhow::Error as function return
errors and annotating things that can fail with "with_context".
Add the column/field name to the end of the message.
This is important because some dependency versions don't actually work,
e.g. thiserror = "^1.0" allows 1.0.0, but actually >=1.0.7 is what is
required for rubbl to compile. This commit also removes all of the "^"
versions because (IMHO) it's clearer to use the major.minor.patch layout
and it does the same thing.

This commit removes version ranges on ndarray and num-complex (I
introduced these in the first place, thinking they were a good idea).
Why are version ranges bad? At the time of writing, 0.15.6 is the latest
version of ndarray. If my code depends on ndarray 0.15.0 and rubbl
depends on ndarray >= 0.8, cargo will determine that ndarray 0.15.6 is
fit for purpose. But, if tomorrow ndarray releases 0.16.0, then cargo
will inspect these requirements and determine that 0.16.0 is best for
rubbl and 0.15.6 is best for my code. Now I depend on two versions of
ndarray, and I can't interact my ndarray code with rubbl's ndarray code.
This is a huge pain, so it's (IHMO) simpler to have libraries (like
rubbl) depend on their oldest semver compatible versions, i.e. today
that is 0.15.0. If/when ndarray 0.16.0 is released, then others
depending on ndarray 0.15.0 and rubbl won't have their code break, and
rubbl should be eventually updated to support ndarray 0.16.0, allowing
rubbl consumers to also update.

clap is a bit of an exception; 4.0.0 probably works, but clap is mostly
intended for binary usage, not library usage, so I think it makes the
most sense to enforce a modern version of clap with lots of bug fixes.
Make anyhow, clap and termcolor optional dependencies in rubbl_core;
they are now activated by the "notifications" feature. This means that
downstream consumers of e.g. rubbl_casacore don't pull these
dependencies in which aren't actually used in the library.

Remove approx as a dependency. This was only in the hope of reducing
friction between date-type-exporting crates like num-complex, but, (now
that we all understand this a bit better,) this doesn't do anything
here.
@pkgw
Copy link
Owner

pkgw commented Nov 28, 2022

Thanks once again for putting in the work on this! I hope/aspire to review this swiftly, and with my release automation tooling it will be easy to put out new releases after merge.

As for backtraces, I think that the backtrace API was just stabilized in the most recent Rust release, after being unstable for a very long time, so now they should be available everywhere that's running the most recent compilers.

@cjordan
Copy link
Contributor Author

cjordan commented Nov 28, 2022

I hope/aspire to review this swiftly

Thank you. Please take your time; there's no huge rush here.

As for backtraces, I think that the backtrace API was just stabilized in the most recent Rust release, after being unstable for a very long time, so now they should be available everywhere that's running the most recent compilers.

Ah, cool. I'll try to remember to update the relevant MSRVs in the Cargo.toml files.

@cjordan
Copy link
Contributor Author

cjordan commented Nov 30, 2022

So I just had a quick look at when backtraces were made stable (apparently the most recent version, 1.65) and adding this minimum supported Rust version (MSRV) to the core/Cargo.toml. Adding rust-version = "1.65" does what you'd expect, but, with that line commented, the code still passes tests on older versions of Rust. This suggests to me that the standard library backtraces aren't being used, otherwise the compiler would complain that they're unstable.

pkgw added 5 commits January 18, 2023 14:21
I'm pretty sure that the traversal logic won't add anything in these
specific examples, but I'd like the example code to show the thorough
way of doing things.
... with some updated and expanded documentation. I think it provides
nice syntactic sugar.
@pkgw
Copy link
Owner

pkgw commented Jan 18, 2023

OK, well my aspiration for prompt review did not come to pass. But after being reminded I have now reviewed this! Thanks again for taking on this effort and doing a nice job with this somewhat tedious work.

I've pushed some updates to your branch:

  • Merged with latest master and fixed conflicts
  • Re-add error chain traversal in the FITS examples for thoroughness
  • Bring back the ctry! macro — it provides syntactic sugar that I think is nice and handy
  • Turn .with_context(|| "constant") (back) into .context("constant") — it's just a bit more concise, and (unlike the use case filled by with_context(|| format!()) or ctry!) the performance is the same

As a last item, I am not sure if the updates to the dependency version specifications are doing exactly what's described in the commit message. I like the caret syntax rather than sigil-less syntax, but that's not a big deal either way, and boosting the minimum versions of various dependencies to be accurate is good. But for the ndarray minimum versioning, I don't quite follow that the change is helpful. If Rubbl's dependency is >=0.8 and yours is 0.15, I'd really expect the Cargo dependency resolver to say "hey, both of those are compatible with ndarray 0.15, I'll use that for both", even if 0.16 is out there. If Rubbl's dependency becomes 0.15 as well, then when 0.16 comes out we have to make new releases that indicate compatibility with the new version, and so if an upstream consumer updates to 0.16 without realizing this we'll get the two-different-versions problem. Basically, having the lower-level library's range of version compatibility be as wide as possible seems like the better state of affairs. And, to the best of my understanding of how Cargo resolves dependencies, we really shouldn't get two different ndarrays unless there is something about the way that the deps are specified that means that two different versions have to be used.

(Based on all of the above I think you already know this, but I'll mention: one thing that tripped me up for a while is that in Cargo's semver implementation, a dep on a pre-1.0 version like ^0.X is not compatible with 0.(X+1), while it is compatible post-1.0. This is actually even more generous than the semver spec says you should be.)

@cjordan
Copy link
Contributor Author

cjordan commented Jan 19, 2023

Thank you for your time, @pkgw. I've had a look at your changes and I have no opinions on it, so I think things are good to go.

As for the ndarray versioning; your thinking was my thinking when I introduced the version range. Unfortunately, Cargo is unintuitive here. You can verify that you get two versions of ndarray pulled in if you create a completely new project (say "frobnicate"), depend on rubbl_casatables and ndarray = 0.13.0. I think the way to think of this is:

  1. Cargo sees that frobnicate needs an ndarray satisfying "0.13.0". It's free to pick the latest semver compatible release, so it goes for 0.13.1
  2. Cargo also sees the rubbl_casatables dep, which needs ndarray satisfying ">=0.8". 0.15.6 is the latest version satisfying that constraint, so it gets added in.
  3. et voila, you have an annoying situation.

It is possible to fix this after Cargo's automatically resolved dep versions by running cargo update -p [email protected] --precise 0.13.1; this will make use of exactly one version of ndarray, and you can go on your merry way. But then you have to remember to do this all the time, in your testing, builds, CI, etc...

So as much as I dislike the situation, I think the lesser evil is to have breaking releases of the rubbl family track breaking releases of ndarray and num-complex etc. Happy to have your thoughts on that. As you point out, obviously it'd be nice for these deps to reach 1.0 so that we don't need to have "frequent" releases, but I guess those maintainers don't want to lock themselves into a stable API yet if ever.

@pkgw
Copy link
Owner

pkgw commented Jan 19, 2023

@cjordan OK, I'd like to try digging into the Cargo situation a little bit more. My read of the documentation is that what you're describing shouldn't be happening; maybe we have something funny about our situation. Or maybe I'm misreading the docs and/or they're inaccurate.

@pkgw
Copy link
Owner

pkgw commented Jan 19, 2023

@pkgw
Copy link
Owner

pkgw commented Jan 20, 2023

OK, I dug into this a bit more; see links in the above forum thread, which basically resolve to rust-lang/cargo#9029 . The TLDR is that the behavior you've identified is indeed Just How It Is for now, and while there is some interest in improving that, nothing's going to be changing soon. Given that I think your change is the best we can do, but I'll add some stuff to the README(s) to hopefully steer future people in the right direction.

@pkgw pkgw merged commit b826979 into pkgw:master Jan 20, 2023
@cjordan
Copy link
Contributor Author

cjordan commented Jan 20, 2023

Thank you for time here, checking, testing, merging and spending time rousing the community of the issue.

I know there's work being done w.r.t. making casatables faster, but could a new release with these changes be cut, please? Hopefully any improvements that come out of the other work could manifest as patch versions as probably dont break semver.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants