Skip to content

Set the process exit code

By default a tool exits 1 on any error. To exit with something else — 64 for a usage error, 77 for a permission failure, whatever your callers expect — attach the code to the error and read it back in main.

Attach the code where the error is raised

use rtb_error::WithExitCode;

fn parse_args() -> miette::Result<Args> {
    let name = std::env::args().nth(1)
        .ok_or(GreetError::NoName.with_exit_code(64))?;
    Ok(Args { name })
}

with_exit_code is available on any Diagnostic + Send + Sync + 'static. It wraps the error in an ExitCoded, which renders exactly as the original did — nothing about the diagnostic on screen changes.

Read it back in main

main has to return std::process::ExitCode. A main returning miette::Result<()> exits 1 on every error, so the attached code would never be applied:

use std::process::ExitCode;
use rtb_error::exit_code_of;

fn main() -> ExitCode {
    rtb_error::hook::install_report_handler();
    rtb_error::hook::install_panic_hook();

    match run() {
        Ok(()) => ExitCode::SUCCESS,
        Err(report) => {
            eprintln!("Error: {report:?}");
            ExitCode::from(exit_code_of(&report).unwrap_or(1))
        }
    }
}

Two details in that block are load-bearing. {report:?} and not {report}Debug is what routes through the installed hook, Display prints the bare message. And unwrap_or(1) is your default for every error that carries no code, which is most of them.

Verify it with the shell:

$ greet
Error: greet::no_name

  x no name given
  help: pass a name: `greet Ada`

$ echo $?
64

Do not box an ExitCoded into Error::Other

exit_code_of is a downcast. This loses the code, silently:

let coded = MyError::Bad.with_exit_code(64);
let err = rtb_error::Error::Other(Box::new(coded));   // code is now unreachable

The report downcasts to rtb_error::Error, not to ExitCoded, so exit_code_of returns None and the boundary falls back to 1. Attach the code at the outermost layer, after any conversion into Error.

Report::wrap_err is safe — adding context does not lose the code.

Do not use 0

with_exit_code(0) compiles and is applied. The process then reports success while printing a diagnostic, which is almost always a bug in the caller. There is no check for it.

Which codes to use

rtb-error has no opinion — the type is u8, and 0 to 255 are all accepted. If your tool has no existing convention, sysexits.h is the usual starting point: 64 for a usage error, 66 for a missing input file, 77 for a permission failure. Whatever you choose, document it in your own tool's reference — a caller scripting around your exit codes cannot discover them from the binary.