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

Recommendation on mixing exit codes with anyhow #247

Open
chipsenkbeil opened this issue Jul 25, 2022 · 2 comments
Open

Recommendation on mixing exit codes with anyhow #247

chipsenkbeil opened this issue Jul 25, 2022 · 2 comments

Comments

@chipsenkbeil
Copy link

chipsenkbeil commented Jul 25, 2022

With Rust 1.61.0 out, the Termination trait and associated ExitCode 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.

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::<MyErrorType1>().is_some() {
                    ExitCode::from(11)
                } else if self.downcast_ref::<MyErrorType2>().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

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

// Assume that this type implements:
// 1. Display that yields the context
// 2. std::error::Error
struct ExitCodeError {
    error: Box<dyn std::error::Error + Send + Sync>, 
    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::<ExitCodeError>() {
                    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

use std::process::ExitCode;

fn main() -> anyhow::Result<()> {
    std::fs::read("some/path")
        .exit_context(ExitCode::from(22), "Failed with specific context")?;
    // ...
}
@chipsenkbeil chipsenkbeil changed the title Recommendation on mixing error codes with anyhow Recommendation on mixing exit codes with anyhow Jul 25, 2022
@ofek
Copy link

ofek commented Nov 4, 2022

@dtolnay What would you recommend?

@benaryorg
Copy link

To bring in a specific use case; some (most?) crond implementations support the use of EAGAIN as an exit code to trigger a retry.
I am currently running into an issue with software where it would be incredibly useful to tag certain error types created with thiserror as being transient errors which can be retried and have the others be permanent errors.
A similar thing could be achieved by using Result<Termination> as in the example provided by OP where I have another layer of main and wrap handle this in an elaborate match statement, but having this integrated in either anyhow would be nice, and it would be even better if it was implemented in a way that thiserror could integrate with.

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

No branches or pull requests

3 participants