Rust Exit Codes
An exit code is a number a program returns to the operating system when it ends. Code 0 means success.
main returns the code
Give main the ExitCode return type, and return the code you choose.
use std::process::ExitCode;
fn main() -> ExitCode {
ExitCode::SUCCESS
}process::exit skips cleanup
The Guard's Drop would print a line if it ran. It never runs.
struct Guard;
impl Drop for Guard {
fn drop(&mut self) {
println!("guard dropped: files flushed");
}
}
fn main() {
let _g = Guard;
println!("about to exit");
std::process::exit(3);
}Returning runs the cleanup
The same Guard, but main returns a code instead of calling exit. This time the Drop line prints. The process still exits with code 3.
fn main() -> ExitCode {
let _g = Guard;
println!("about to return");
ExitCode::from(3)
}A different code per failure
Match on the situation, return the code that names it.
match args.get(1).map(|s| s.as_str()) {
None => ExitCode::from(2), // wrong usage
Some("boom") => ExitCode::FAILURE, // the job failed
Some(_) => ExitCode::SUCCESS, // all good
}Map each failure to its code
One small function owns the mapping. main stays free of bare numbers. Run its three ways and the codes come out 2, 1, and 0.
fn code_for(fail: &Fail) -> ExitCode {
match fail {
Fail::Usage => ExitCode::from(2),
Fail::Job => ExitCode::from(1),
}
}Example
use std::process::ExitCode;
fn run(args: &[String]) -> ExitCode {
match args.get(1).map(|s| s.as_str()) {
None => {
eprintln!("usage: greeter <name>");
ExitCode::from(2)
}
Some("boom") => {
eprintln!("error: the name 'boom' is banned");
ExitCode::FAILURE
}
Some(name) => {
println!("hello, {name}");
ExitCode::SUCCESS
}
}
}
fn main() -> ExitCode {
let args: Vec<String> = std::env::args().collect();
run(&args)
}Key points
- ExitCode works because main may return any Termination type.
- The same trait is why returning a Result from main works too.
- Reach for process::exit only in a deep helper that cannot return to main.
- Use process::exit only when skipping cleanup is safe there.
- Two usage conventions exist.
- Clap and most GNU tools use 2 for a usage error.
- The older sysexits list starts its special codes at 64.
- Document your codes in --help or the README.
- A code nobody can look up is just a number.
- Scripts read the code to tell a usage mistake from a failed job.
In this example, main returns whatever run decides, and run sorts the call into three cases. Run with no name, the program prints usage: greeter <name> (to stderr) and exits with code 2 — wrong usage. Run it as greeter ada and it prints hello, ada and exits 0. Run it as greeter boom and it prints its error and exits 1. All three runs were tried for real: the codes were 2, 0 and 1, exactly as the code promises. A shell script can now react to each case on its own.
Note
An Err returned from main always exits with code 1 and prints Error: first.
ExitCode is for when the number itself must carry meaning.
Try it Yourself
Edit the code, then press Run.
Running is simulated · Rust Playground
Exercise
Why prefer returning ExitCode from main over calling process::exit?