Environment and Process Management

Signals and Platform-Specific APIs

Operating systems expose process management features beyond portable spawning and exit codes: Unix systems use signals, process groups, uid/gid credentials, and exec image replacement, while Windows uses creation flags, job objects, and raw argument strings. Rust exposes these platform-native capabilities safely and zero-cost through extension traits in std::os::unix::process and std::os::windows::process.

Platform Extension Traits for std::process

Platform Module Extension Trait Target Type Key Capabilities
std::os::unix::process CommandExt Command .uid(), .gid(), .process_group(), .arg0(), .exec(), unsafe { .pre_exec() }
std::os::unix::process ExitStatusExt ExitStatus .signal(), .core_dumped(), .stopped_signal(), .continued()
std::os::windows::process CommandExt Command .creation_flags(), .raw_arg()
std::os::windows::process ChildExt Child .raw_handle()

Unix Signals and ExitStatusExt

On Unix platforms, a process can be terminated or stopped by asynchronous kernel notifications called signals:

Signal Number (Typical) Default Action Common Cause
SIGINT 2 Terminate User pressed Ctrl+C in the terminal.
SIGQUIT 3 Core Dump User pressed Ctrl+\ in the terminal.
SIGKILL 9 Terminate (cannot be caught) Forceful kill (kill -9 or Child::kill()).
SIGSEGV 11 Core Dump Invalid memory access (segmentation fault).
SIGPIPE 13 Terminate (ignored by Rust runtime) Writing to a pipe or socket whose reader closed.
SIGTERM 15 Terminate (catchable) Graceful shutdown request from process manager/systemd.

Note that the Rust runtime automatically sets SIGPIPE to SIG_IGN (ignore) before fn main() starts so that writing to a closed pipe or socket returns Err(io::ErrorKind::BrokenPipe) rather than silently killing the process.

use std::io;
use std::process::{Command, Stdio};

fn main() -> io::Result<()> {
    let mut cmd = Command::new("rustc");
    cmd.arg("--version").stdout(Stdio::null());

    #[cfg(unix)]
    {
        use std::os::unix::process::CommandExt;
        // Put the child into its own process group (PGID = child PID)
        // so terminal signals can be managed independently
        cmd.process_group(0);
        // Customize argv[0] seen by the child process
        cmd.arg0("custom-rustc-worker");
    }

    let status = cmd.status()?;

    #[cfg(unix)]
    {
        use std::os::unix::process::ExitStatusExt;
        println!(
            "Exited cleanly: {}, signal: {:?}, core_dumped: {}",
            status.success(),
            status.signal(),
            status.core_dumped()
        );
    }

    #[cfg(not(unix))]
    {
        println!("Exited cleanly: {}, signal: None, core_dumped: false", status.success());
    }

    Ok(())
}
Exited cleanly: true, signal: None, core_dumped: false

Replacing the Current Process Image on Unix (CommandExt::exec)

On Unix, CommandExt::exec(&mut self) -> io::Error invokes the execvp system call, replacing the current process memory image in-place with the new program while keeping the same PID and open file descriptors. Because a successful exec never returns to the caller, its return type is io::Error rather than io::Result<()>.

Windows Creation Flags (Windows CommandExt)

On Windows, GUI applications spawning background CLI tools often want to suppress the flashing console window using the CREATE_NO_WINDOW (0x08000000) flag:

#[cfg(windows)]
fn configure_headless(cmd: &mut std::process::Command) {
    use std::os::windows::process::CommandExt;
    const CREATE_NO_WINDOW: u32 = 0x0800_0000;
    cmd.creation_flags(CREATE_NO_WINDOW);
}

fn main() {
    println!("Platform extension traits configured");
}
Platform extension traits configured