Skip to content

Comments

Change error handling from error-chain to anyhow.#777

Merged
nnethercote merged 1 commit intomozilla:masterfrom
nnethercote:use-anyhow
Jun 10, 2020
Merged

Change error handling from error-chain to anyhow.#777
nnethercote merged 1 commit intomozilla:masterfrom
nnethercote:use-anyhow

Conversation

@nnethercote
Copy link
Contributor

Because anyhow is what seems to be the most common error library for
applications these days.

  • The global Result type is now anyhow::Result.

  • In errors.rs, there's no need for any boilerplate to wrap all the foreign
    errors seen: hyper::Error, io:Error, etc.

  • The internal errors that we care about are now separate types, rather
    than within an enum, because that works better when we need to check for them
    by downcasting an anyhow::Error. And it's nice to write
    Err(ProcessError(output)) rather than
    Err(ErrorKind::ProcessError(output)).

  • The Which error was unused and is removed.

  • The most common change is that .chain_err() is changed to
    .context/.with_context().

  • anyhow! is used where necessary, mostly to promote a string to an
    anyhow::Error.

  • Errors within futures: FutureChainErr/.chain_err() is changed to
    FutureContext/fcontext/fwith_context. The f prefix is because I found
    it helpful to distinguish these cases from the simple error cases.

  • BuilderIncoming, SchedulerIncoming, ServerIncoming no longer have an
    Error associated type, we just use anyhow::Error uniformly.

  • e.display_chain() changes to format!("{:?}"), because they both print the
    full cause chain, and the backtrace (if present).

  • A few places where the old code was doing something weird or more
    complicated than seemed necessary, I generally tried to replace it with
    something simpler and more typical. Two places used with_boxed_chain(),
    which doesn't have an equivalent in anyhow, so I did my best to do
    something reasonable.

  • In src/server.rs we now import std::task::Context as TaskContext to
    avoid overshadowing the anyhow::Context trait :(

@nnethercote nnethercote requested a review from froydnj June 5, 2020 04:24
@nnethercote
Copy link
Contributor Author

Apologies for the large review.

I haven't run rustfmt over this, so it should wait until #776 has landed and then I can rebase and reformat.

@luser
Copy link
Contributor

luser commented Jun 5, 2020

I did this same conversion in our work codebase a while ago and have been happy with the outcome, FWIW. I also pulled in thiserror for our custom errors, you might look into that as an additional step but it's not as big of a win as anyhow.

Not that my opinion matters these days but I wouldn't say I love the fcontext / fwith_context methods. Any reason you think matching the function names form anyhow::Context isn't good?

One other thing I found when doing this in our work codebase was that anyhow::Context is implemented for Option so if there are places in the sccache codebase where you're currently handling Options and returning errors you can instead use option.context("whatever")?.

@nnethercote
Copy link
Contributor Author

I also pulled in thiserror for our custom errors, you might look into that as an additional step but it's not as big of a win as anyhow.

I considered it but was convinced by this comment not to use it. Doing it by hand for the three internal errors was easy.

Not that my opinion matters these days but I wouldn't say I love the fcontext / fwith_context methods. Any reason you think matching the function names form anyhow::Context isn't good?

Early on in the conversion I changed FutureChainError::chain_err to fchain_err because I found it hard to know at call sites whether I was dealing with an error or a future. I could live with removing the f prefix, though.

One other thing I found when doing this in our work codebase was that anyhow::Context is implemented for Option so if there are places in the sccache codebase where you're currently handling Options and returning errors you can instead use option.context("whatever")?.

Does that mean I can change this:

let user_dirs = UserDirs::new().ok_or_else(|| anyhow!("Couldn't get user directories"))?;

to this:

let user_dirs = UserDirs::new().context("Couldn't get user directories"))?;

? That would be nice, there are plenty of ok_or_else calls like this in the code. I will try this out on Monday, thanks for the tip.

@nnethercote
Copy link
Contributor Author

@froydnj: I have rebased, addressed Ted's comments, and run rustfmt. It's ready for you to take a look, thanks.

BTW, here is the current diffstats. Not bad:

35 files changed, 554 insertions(+), 633 deletions(-)

Because `anyhow` is what seems to be the most common error library for
applications these days.

- The global `Result` type is now `anyhow::Result`.

- In errors.rs, there's no need for any boilerplate to wrap all the foreign
  errors seen: `hyper::Error`, `io:Error`, etc.

- The internal errors that we care about are now separate types, rather
  than within an enum, because that works better when we need to check for them
  by downcasting an `anyhow::Error`. And it's nice to write
  `Err(ProcessError(output))` rather than
  `Err(ErrorKind::ProcessError(output))`.

- The `Which` error was unused and is removed.

- The most common change is that `.chain_err()` is changed to
  `.context`/`.with_context()`.

- `anyhow!` is used where necessary, mostly to promote a string to an
  `anyhow::Error`.

- Errors within futures: `FutureChainErr`/`.chain_err()` is changed to
  `FutureContext`/`fcontext`/`fwith_context`. The `f` prefix is because I found
  it helpful to distinguish these cases from the simple error cases.

- `BuilderIncoming`, `SchedulerIncoming`, `ServerIncoming` no longer have an
  `Error` associated type, we just use `anyhow::Error` uniformly.

- `e.display_chain()` changes to `format!("{:?}")`, because they both print the
  full cause chain, and the backtrace (if present).

- A few places where the old code was doing something weird or more
  complicated than seemed necessary, I generally tried to replace it with
  something simpler and more typical. Two places used `with_boxed_chain()`,
  which doesn't have an equivalent in `anyhow`, so I did my best to do
  something reasonable.

- In `src/server.rs` we now import `std::task::Context` as `TaskContext` to
  avoid overshadowing the `anyhow::Context` trait :(
Copy link
Contributor

@froydnj froydnj left a comment

Choose a reason for hiding this comment

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

Thank you, this is much nicer.

@nnethercote nnethercote merged commit 79f2968 into mozilla:master Jun 10, 2020
@nnethercote nnethercote deleted the use-anyhow branch June 10, 2020 23:17
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.

3 participants