dtolnay / dtolnay/anyhow

Recommendation on mixing exit codes with anyhow

Open
#247 7 comments 28 reactions 0 assignees View on GitHub
Dominant language
Rust
Stars
6.7k
Forks
224
PR merge metrics
No merged PRs in 30d

Description

With Rust 1.61.0 out, the [`Termination`](https://doc.rust-lang.org/stable/std/process/trait.Termination.html) trait and associated [`ExitCode`](https://doc.rust-lang.org/stable/std/process/struct.ExitCode.html) are now stable. In the past, I maintained a large error enum purely to decipher what error code to return, printed out the error using `eprintln!`, and then exited with `std::process::exit`.

Now, I'm thinking through a way for me to switch over to anyhow because it would streamline so much in my application, but I still need some way to keep track of what exit code to return for different errors. Looking for any advice/thoughts on how I could capture/encode that information when bubbling up errors.

### Implement a wrapper type for `anyhow::Error` at the end

One way would be to create a newtype wrapper for the error and then use downcasting to figure out the underlying error with an associated exit code.

```rust
use std::process::{ExitCode, Termination};

struct AppResult(anyhow::Result<()>);

impl Termination for AppResult {
fn report(self) -> ExitCode {
match self {
Ok(_) => ExitCode::SUCCESS,
Err(x) => {
if self.downcast_ref::().is_some() {
ExitCode::from(11)
} else if self.downcast_ref::().is_some() {
ExitCode::from(22)
} else {
ExitCode::FAILURE
}
}
}
}
}

fn main() -> AppResult {
AppResult(real_main())
}

fn real_main() -> anyhow::Result<()> {
// ...
}
```

### Derive an error type with an exit code

```rust
use std::process::{ExitCode, Termination};

// Assume that this type implements:
// 1. Display that yields the context
// 2. std::error::Error
struct ExitCodeError {
error: Box,
exit_code: ExitCode,
}

struct AppResult(anyhow::Result<()>);

impl Termination for AppResult {
fn report(self) -> ExitCode {
match self {
Ok(_) => ExitCode::SUCCESS,
Err(x) => {
if let Some(x) = self.downcast::() {
x.exit_code
} else {
ExitCode::FAILURE
}
}
}
}
}

fn main() -> AppResult {
AppResult(real_main())
}

fn real_main() -> anyhow::Result<()> {
// ...

// For each error, we have to wrap the error in our ExitCodeError first if we want a unique exit code
// This seems really verbose still, so maybe there's a way to simplify
let value = do_something().map_err(|error| ExitCodeError {
error: Box::new(error),
exit_code: ExitCode::from(22)
})?;

// ...
}
```

### Some cleaner way?

Ideally, I'd love something like

```rust
use std::process::ExitCode;

fn main() -> anyhow::Result<()> {
std::fs::read("some/path")
.exit_context(ExitCode::from(22), "Failed with specific context")?;
// ...
}
```

Contributor guide

No contributing guide indexed for this repository

Research direction

No repository file or test is named. Start by reviewing the shown main/real_main flow alongside Rust's Termination and ExitCode APIs and anyhow's error propagation; done would require a decided, maintainable recommendation for associating errors with exit codes, rather than another open-ended design discussion.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
cli
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.