Environment and Process Management
Exit Status
An ExitStatus (std::process::ExitStatus) represents the termination state of a finished child process. It indicates whether the process succeeded (code == 0) or failed, and whether it exited normally via an exit code or was terminated by an operating system signal.
io::Result<ExitStatus> vs ExitStatus::success()
When running a command via status(), output(), or wait(), there are two distinct layers of success or failure:
Command::new("tool").status()
|
+-- Err(io::Error) OS failed to launch the binary (e.g., NotFound, PermissionDenied)
|
+-- Ok(ExitStatus) Binary launched and ran to completion (or signal death)
|
+-- status.success() == true Process exited with code 0
+-- status.success() == false Process exited with non-zero code OR died via signal
| Method | Return Type | Meaning |
|---|---|---|
status.success() |
bool |
Returns true if and only if the process exited normally with exit code 0. |
status.code() |
Option<i32> |
Returns Some(code) if the process exited normally, or None on Unix if terminated by a signal. |
ExitStatusExt::signal(&status) |
Option<i32> |
(Unix only) Returns Some(sig) if the child was killed by signal sig (e.g., 9 for SIGKILL). |
Inspecting Exit Codes
use std::io;
use std::process::{Command, Stdio};
fn main() -> io::Result<()> {
// Passing `--version` succeeds with exit code 0
let ok_status = Command::new("rustc")
.arg("--version")
.stdout(Stdio::null())
.status()?;
println!("Success: {}, Code: {:?}", ok_status.success(), ok_status.code());
// Passing an invalid flag fails with a non-zero exit code (still Ok(ExitStatus)!)
let err_status = Command::new("rustc")
.arg("--definitely-not-a-valid-rustc-flag")
.stdout(Stdio::null())
.stderr(Stdio::null())
.status()?;
println!("Success: {}, Code: {:?}", err_status.success(), err_status.code());
Ok(())
}
Success: true, Code: Some(0)
Success: false, Code: Some(1)
When status.code() Returns None on Unix
On Windows, every terminated process has a 32-bit exit code, so status.code() always returns Some(i32). On Unix, the wait status word distinguishes normal exits (_exit(code)) from signal deaths (SIGTERM, SIGKILL, SIGSEGV). When a Unix child is killed by a signal, status.code() returns None:
use std::io;
use std::process::{Command, Stdio};
fn main() -> io::Result<()> {
// Spawn a child waiting on a piped stdin so it stays alive until killed
let mut child = Command::new("rustc")
.arg("-")
.stdin(Stdio::piped())
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()?;
// Forcefully terminate the child (`SIGKILL` on Unix, `TerminateProcess` on Windows)
child.kill()?;
let status = child.wait()?;
println!("Killed child success(): {}", status.success());
#[cfg(unix)]
{
use std::os::unix::process::ExitStatusExt;
println!("Unix code(): {:?}, signal(): {:?}", status.code(), status.signal());
}
#[cfg(windows)]
{
println!("Windows code() is Some: {}", status.code().is_some());
}
Ok(())
}
Killed child success(): false
Unix code(): None, signal(): Some(9)