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:
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.