Process #

Every running program is a process. Rust provides std::process for interacting with processes — both themselves and launched child processes. This need arises in many scenarios: executing shell commands from within a program, building a CLI tool that calls another tool, reading configuration from environment variables, or controlling how a program exits when an error occurs. std::process also includes access to command line arguments and environment variables that form the program execution context. This article covers all these aspects comprehensively — from simple child process launches to more complex pipeline patterns.

std::process::exit and Exit Code #

The most basic way to interact with a process is to control how it exits. In Rust, main() returning () always exits with code 0 (success). To exit with another code, use process::exit.

use std::process;

fn main() {
    let args: Vec<String> = std::env::args().collect();

    if args.len() < 2 {
        eprintln!("Penggunaan: {} <nama>", args[0]);
        process::exit(1);  // exits with an error code
    }

    println!("Halo, {}!", args[1]);
    // exits with code 0 implicitly
}

A more idiomatic way is to use main() which returns Result — this allows the use of the ? operator throughout the main function:

use std::error::Error;
use std::fs;

fn main() -> Result<(), Box<dyn Error>> {
    let konten = fs::read_to_string("config.txt")?;
    println!("Konfigurasi: {}", konten.trim());
    Ok(())
}
// If an error occurs, Rust prints an error message and exits with code 1
// If Ok(()), exit with code 0

For more explicit control, implement the “main calling run” pattern:

use std::process;

fn run() -> Result<(), String> {
    let args: Vec<String> = std::env::args().collect();
    if args.len() < 3 {
        return Err(format!("Butuh 2 argumen, dapat {}", args.len() - 1));
    }
    let a: i32 = args[1].parse().map_err(|_| format!("'{}' bukan angka", args[1]))?;
    let b: i32 = args[2].parse().map_err(|_| format!("'{}' bukan angka", args[2]))?;
    println!("Hasil: {}", a + b);
    Ok(())
}

fn main() {
    if let Err(e) = run() {
        eprintln!("Error: {}", e);
        process::exit(1);
    }
}

Exit Code Convention #

Kode keluar yang umum digunakan:
  0   — Sukses
  1   — Error umum
  2   — Kesalahan penggunaan (argumen salah, dsb)
  126 — Perintah ditemukan tapi tidak bisa dieksekusi
  127 — Perintah tidak ditemukan
  128 — Invalid exit argument
  130 — Program dihentikan dengan Ctrl+C (SIGINT)
use std::process;

// Define a constant for a meaningful exit code
const EXIT_OK: i32 = 0;
const EXIT_ERROR: i32 = 1;
const EXIT_USAGE: i32 = 2;

fn main() {
    let args: Vec<String> = std::env::args().collect();

    if args.contains(&"--help".to_string()) {
        println!("Bantuan penggunaan program...");
        process::exit(EXIT_OK);
    }

    if args.len() < 2 {
        eprintln!("Error: argumen tidak cukup");
        eprintln!("Gunakan --help untuk bantuan");
        process::exit(EXIT_USAGE);
    }

    match jalankan_program(&args[1]) {
        Ok(_) => process::exit(EXIT_OK),
        Err(e) => {
            eprintln!("Error: {}", e);
            process::exit(EXIT_ERROR);
        }
    }
}

fn jalankan_program(_arg: &str) -> Result<(), String> {
    Ok(())
}

Command Line Arguments #

std::env::args() provides access to arguments provided when the program is run.

use std::env;

fn main() {
    // args() returns an iterator — the first element is the program name
    let args: Vec<String> = env::args().collect();

    println!("Nama program: {}", args[0]);
    println!("Jumlah argumen: {}", args.len() - 1);

    for (i, arg) in args.iter().enumerate().skip(1) {
        println!("Argumen {}: {}", i, arg);
    }

    // Parse simple arguments
    let mut verbose = false;
    let mut output_file: Option<String> = None;
    let mut input_files: Vec<String> = Vec::new();

    let mut iter = args.iter().skip(1);  // skip program name
    while let Some(arg) = iter.next() {
        match arg.as_str() {
            "-v" | "--verbose" => verbose = true,
            "-o" | "--output" => {
                output_file = iter.next().cloned();
            }
            arg if arg.starts_with("--output=") => {
                output_file = Some(arg.trim_start_matches("--output=").to_string());
            }
            arg if arg.starts_with('-') => {
                eprintln!("Flag tidak dikenal: {}", arg);
                std::process::exit(2);
            }
            file => input_files.push(file.to_string()),
        }
    }

    if verbose {
        println!("Mode verbose aktif");
        println!("Output: {:?}", output_file);
        println!("Input files: {:?}", input_files);
    }
}
For more complex command line argument parsing — subcommands, argument types, validation, and automatic help text — use the clap crate. Almost all Rust CLI tools in production use clap because manual parsing quickly becomes unmaintainable for programs with more than a few flags.
[dependencies]
clap = { version = "4", features = ["derive"] }
use clap::Parser;

#[derive(Parser, Debug)]
#[command(name = "myapp", about = "Contoh aplikasi CLI")]
struct Args {
    /// Input file to be processed
    #[arg(required = true)]
    input: Vec<String>,

    /// Output files
    #[arg(short, long, default_value = "output.txt")]
    output: String,

    /// Enable verbose mode
    #[arg(short, long)]
    verbose: bool,

    /// Number of worker threads
    #[arg(short, long, default_value_t = 4)]
    threads: u32,
}

fn main() {
    let args = Args::parse();
    println!("Input: {:?}", args.input);
    println!("Output: {}", args.output);
    println!("Verbose: {}", args.verbose);
    println!("Threads: {}", args.threads);
}

Environment Variables #

Environment variables are a common way to configure programs without changing code or configuration files.

use std::env;

fn main() {
    // Reads one environment variable
    match env::var("HOME") {
        Ok(nilai) => println!("HOME: {}", nilai),
        Err(env::VarError::NotPresent) => println!("HOME tidak disetel"),
        Err(env::VarError::NotUnicode(v)) => println!("HOME bukan UTF-8: {:?}", v),
    }

    // With default value
    let port = env::var("PORT").unwrap_or_else(|_| String::from("8080"));
    let host = env::var("HOST").unwrap_or(String::from("localhost"));

    // Parse directly to the desired type
    let maks_koneksi: u32 = env::var("MAX_CONNECTIONS")
        .ok()
        .and_then(|s| s.parse().ok())
        .unwrap_or(100);

    println!("Server: {}:{}", host, port);
    println!("Maks koneksi: {}", maks_koneksi);

    // Iterates all environment variables
    println!("\nSemua environment variables:");
    for (kunci, nilai) in env::vars() {
        if kunci.starts_with("RUST") {
            println!("  {} = {}", kunci, nilai);
        }
    }

    // Change the environment variables for this process
    // (only applies to current process, does not affect parent)
    env::set_var("MY_APP_ENV", "production");
    println!("{}", env::var("MY_APP_ENV").unwrap());  // "production"

    // Delete environment variables
    env::remove_var("MY_APP_ENV");
    println!("{:?}", env::var("MY_APP_ENV"));  // Err(NotPresent)

    // Current directory
    let cwd = env::current_dir().expect("Gagal mendapat direktori saat ini");
    println!("CWD: {}", cwd.display());

    // Executable directory
    let exe = env::current_exe().expect("Gagal mendapat path executable");
    println!("Executable: {}", exe.display());
}

Environment Variables Based Configuration #

A very common pattern in production applications is to read all configuration from environment variables at startup:

use std::env;

#[derive(Debug)]
struct Konfigurasi {
    database_url: String,
    port: u16,
    debug: bool,
    log_level: String,
    jwt_secret: String,
}

impl Konfigurasi {
    fn dari_env() -> Result<Self, String> {
        Ok(Konfigurasi {
            database_url: env::var("DATABASE_URL")
                .map_err(|_| "DATABASE_URL harus disetel".to_string())?,

            port: env::var("PORT")
                .unwrap_or_else(|_| "8080".to_string())
                .parse::<u16>()
                .map_err(|_| "PORT harus berupa angka 0-65535".to_string())?,

            debug: env::var("DEBUG")
                .map(|v| v == "true" || v == "1")
                .unwrap_or(false),

            log_level: env::var("LOG_LEVEL")
                .unwrap_or_else(|_| "info".to_string()),

            jwt_secret: env::var("JWT_SECRET")
                .map_err(|_| "JWT_SECRET harus disetel".to_string())?,
        })
    }
}

fn main() {
    // Load configuration on startup
    let config = match Konfigurasi::dari_env() {
        Ok(c) => c,
        Err(e) => {
            eprintln!("Error konfigurasi: {}", e);
            std::process::exit(1);
        }
    };

    println!("Konfigurasi dimuat: {:?}", config);
}

Command — Executes Child Process #

std::process::Command is an API for launching and interacting with child processes.

use std::process::Command;

fn main() {
    // Executes simple commands
    let status = Command::new("echo")
        .arg("Halo dari proses anak!")
        .status()  // execute and wait, returns exit status
        .expect("Gagal menjalankan echo");

    println!("Exit status: {}", status.success());

    // Command with multiple arguments
    let status = Command::new("ls")
        .args(["-la", "/tmp"])
        .status()
        .expect("Gagal menjalankan ls");

    // Capture output — output is not displayed to the terminal
    let output = Command::new("date")
        .output()  // run and catch stdout + stderr
        .expect("Gagal menjalankan date");

    println!("Status: {}", output.status.success());
    println!("Stdout: {}", String::from_utf8_lossy(&output.stdout));
    println!("Stderr: {}", String::from_utf8_lossy(&output.stderr));

    // ANTI-PATTERN: uses shell for simple commands — unnecessary overhead
    let output = Command::new("sh")
        .arg("-c")
        .arg("echo hello")
        .output()
        .unwrap();

    // CORRECT: call the binary directly if possible
    let output = Command::new("echo")
        .arg("hello")
        .output()
        .unwrap();
    println!("{}", String::from_utf8_lossy(&output.stdout).trim());
}

Handling Output and Errors #

use std::process::Command;

fn jalankan_perintah(program: &str, args: &[&str]) -> Result<String, String> {
    let output = Command::new(program)
        .args(args)
        .output()
        .map_err(|e| format!("Gagal menjalankan '{}': {}", program, e))?;

    if output.status.success() {
        Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
    } else {
        let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string();
        Err(format!(
            "'{}' keluar dengan kode {}: {}",
            program,
            output.status.code().unwrap_or(-1),
            stderr
        ))
    }
}

fn main() {
    match jalankan_perintah("git", &["rev-parse", "--short", "HEAD"]) {
        Ok(commit) => println!("Commit saat ini: {}", commit),
        Err(e) => eprintln!("Error: {}", e),
    }

    match jalankan_perintah("cat", &["file_tidak_ada.txt"]) {
        Ok(konten) => println!("Konten: {}", konten),
        Err(e) => eprintln!("Error: {}", e),
    }

    // Checks whether the program is available in the PATH
    fn program_tersedia(nama: &str) -> bool {
        Command::new(nama)
            .arg("--version")
            .output()
            .map(|o| o.status.success())
            .unwrap_or(false)
    }

    println!("git tersedia: {}", program_tersedia("git"));
    println!("docker tersedia: {}", program_tersedia("docker"));
}

Environment and Working Directory for Child Processes #

Command allows granular configuration of child processes’ environments and working directories.

use std::process::Command;
use std::collections::HashMap;

fn main() {
    // Set environment variables for child processes only
    let output = Command::new("printenv")
        .arg("MY_VAR")
        .env("MY_VAR", "nilai_dari_parent")
        .output()
        .unwrap();
    println!("{}", String::from_utf8_lossy(&output.stdout).trim());

    // Remove the specified environment variable from the child process
    let output = Command::new("printenv")
        .arg("HOME")
        .env_remove("HOME")
        .output()
        .unwrap();
    println!("HOME di anak: '{}'", String::from_utf8_lossy(&output.stdout).trim());

    // Clear all env vars, setting only what is needed — for isolated processes
    let output = Command::new("env")
        .env_clear()
        .env("PATH", "/usr/bin:/bin")
        .env("HOME", "/tmp")
        .output()
        .unwrap();
    println!("Env terisolasi:\n{}", String::from_utf8_lossy(&output.stdout));

    // Change the working directory of the child process
    let output = Command::new("pwd")
        .current_dir("/tmp")
        .output()
        .unwrap();
    println!("CWD proses anak: {}", String::from_utf8_lossy(&output.stdout).trim());

    // Combination: run cargo in the specified project directory
    let output = Command::new("cargo")
        .args(["build", "--release"])
        .current_dir("/path/ke/proyek")
        .env("RUST_LOG", "info")
        .output();

    match output {
        Ok(o) if o.status.success() => println!("Build sukses"),
        Ok(o) => eprintln!("Build gagal: {}", String::from_utf8_lossy(&o.stderr)),
        Err(e) => eprintln!("Cargo tidak ditemukan: {}", e),
    }
}

Piping stdin and stdout #

For two-way interaction with child processes — sending input and reading output — use Stdio::piped().

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

fn main() {
    // Send input to child process stdin
    let mut child = Command::new("cat")  // cat reads stdin and prints to stdout
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .spawn()  // spawn runs without waiting
        .expect("Gagal spawn cat");

    // Send data to child process stdin
    let stdin = child.stdin.as_mut().expect("Gagal ambil stdin");
    stdin.write_all(b"halo dari parent\n").expect("Gagal tulis ke stdin");
    stdin.write_all(b"baris kedua\n").expect("Gagal tulis ke stdin");
    // stdin will close on drop — signaling EOF to the child process

    // Wait for the process to complete and take the output
    let output = child.wait_with_output().expect("Gagal tunggu proses anak");
    println!("Output dari cat:\n{}", String::from_utf8_lossy(&output.stdout));

    // Pipeline: output of one process to input of another process
    // Equivalent to: echo "hello rust world" | tr 'a-z' 'A-Z' | wc -w
    let echo = Command::new("echo")
        .arg("halo dunia rust")
        .stdout(Stdio::piped())
        .spawn()
        .expect("Gagal spawn echo");

    let tr = Command::new("tr")
        .args(["a-z", "A-Z"])
        .stdin(echo.stdout.unwrap())  // output echo → stdin tr
        .stdout(Stdio::piped())
        .spawn()
        .expect("Gagal spawn tr");

    let output = tr.wait_with_output().expect("Gagal tunggu tr");
    println!("Setelah tr: {}", String::from_utf8_lossy(&output.stdout).trim());
    // "HELLO RUST WORLD"
}

Running Process in Background #

use std::process::{Command, Stdio};
use std::time::Duration;
use std::thread;

fn main() {
    // spawn() returns Child — process running in the background
    let mut child = Command::new("sleep")
        .arg("10")
        .spawn()
        .expect("Gagal spawn sleep");

    println!("Proses anak berjalan dengan PID: {}", child.id());

    // Perform other work while the process is running
    thread::sleep(Duration::from_millis(100));

    // Check whether the process is still running
    match child.try_wait() {
        Ok(Some(status)) => println!("Proses sudah selesai: {}", status),
        Ok(None) => println!("Proses masih berjalan"),
        Err(e) => eprintln!("Error cek status: {}", e),
    }

    // Force stop the process
    child.kill().expect("Gagal membunuh proses");
    let status = child.wait().expect("Gagal tunggu setelah kill");
    println!("Proses dihentikan: {:?}", status);

    // Timeout: wait a maximum of N seconds, then kill
    fn jalankan_dengan_timeout(
        program: &str,
        args: &[&str],
        timeout: Duration,
    ) -> Result<std::process::Output, String> {
        let mut child = Command::new(program)
            .args(args)
            .stdout(Stdio::piped())
            .stderr(Stdio::piped())
            .spawn()
            .map_err(|e| format!("Gagal spawn: {}", e))?;

        let deadline = std::time::Instant::now() + timeout;

        loop {
            match child.try_wait() {
                Ok(Some(_)) => {
                    return child.wait_with_output()
                        .map_err(|e| format!("Gagal ambil output: {}", e));
                }
                Ok(None) => {
                    if std::time::Instant::now() >= deadline {
                        child.kill().ok();
                        return Err(format!("Timeout setelah {:?}", timeout));
                    }
                    thread::sleep(Duration::from_millis(10));
                }
                Err(e) => return Err(format!("Error: {}", e)),
            }
        }
    }

    match jalankan_dengan_timeout("sleep", &["5"], Duration::from_millis(100)) {
        Ok(output) => println!("Selesai: {:?}", output.status),
        Err(e) => println!("Error: {}", e),  // "Timeout after 100ms"
    }
}

Shell Scripting Patterns in Rust #

Rust is often used as a shell script replacement for tasks that require higher reliability. The following are common patterns.

use std::process::Command;
use std::fs;
use std::path::Path;

// Helper to run commands like in shell script
fn sh(perintah: &str) -> Result<String, String> {
    let output = Command::new("sh")
        .arg("-c")
        .arg(perintah)
        .output()
        .map_err(|e| e.to_string())?;

    if output.status.success() {
        Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
    } else {
        Err(String::from_utf8_lossy(&output.stderr).trim().to_string())
    }
}

// More secure: run binary directly without shell
fn jalankan(program: &str, args: &[&str]) -> Result<String, String> {
    let output = Command::new(program)
        .args(args)
        .output()
        .map_err(|e| format!("Gagal jalankan '{}': {}", program, e))?;

    if output.status.success() {
        Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
    } else {
        Err(format!(
            "Exit {}: {}",
            output.status.code().unwrap_or(-1),
            String::from_utf8_lossy(&output.stderr).trim()
        ))
    }
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Check required dependencies
    for tool in &["git", "cargo", "docker"] {
        match jalankan(tool, &["--version"]) {
            Ok(versi) => println!("✓ {}: {}", tool, versi.lines().next().unwrap_or("")),
            Err(_) => {
                eprintln!("✗ {} tidak ditemukan — pastikan sudah terinstal", tool);
                std::process::exit(1);
            }
        }
    }

    // Retrieve git information
    let branch = jalankan("git", &["rev-parse", "--abbrev-ref", "HEAD"])?;
    let commit = jalankan("git", &["rev-parse", "--short", "HEAD"])?;
    let status = jalankan("git", &["status", "--porcelain"])?;

    println!("\nStatus git:");
    println!("  Branch: {}", branch);
    println!("  Commit: {}", commit);
    println!("  Perubahan: {}", if status.is_empty() { "tidak ada" } else { "ada perubahan" });

    // Build project
    println!("\nMemulai build...");
    let output = Command::new("cargo")
        .args(["build", "--release"])
        .status()?;

    if !output.success() {
        return Err("Build gagal".into());
    }
    println!("Build sukses!");

    // Safer file operations from shell
    let versi = "1.0.0";
    let nama_arsip = format!("release-v{}.tar.gz", versi);

    // Create a directory if it doesn't already exist
    fs::create_dir_all("dist")?;

    // Copy binary
    let src = Path::new("target/release/myapp");
    let dst = Path::new("dist/myapp");
    if src.exists() {
        fs::copy(src, dst)?;
        println!("Binary disalin ke dist/");
    }

    Ok(())
}

Capture Process Signal #

Rust standard library does not provide direct signal handling due to platform complexity. For this need, crate ctrlc (for Ctrl+C) or signal-hook (for complete signal) is the standard choice.

[dependencies]
ctrlc = "3"
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;

fn main() {
    let berjalan = Arc::new(AtomicBool::new(true));
    let berjalan_clone = Arc::clone(&berjalan);

    // Capture Ctrl+C
    ctrlc::set_handler(move || {
        println!("\nMenerima Ctrl+C, menghentikan...");
        berjalan_clone.store(false, Ordering::SeqCst);
    }).expect("Gagal menyetel handler Ctrl+C");

    println!("Program berjalan. Tekan Ctrl+C untuk berhenti.");

    // Stoppable main loop
    let mut iterasi = 0;
    while berjalan.load(Ordering::SeqCst) {
        iterasi += 1;
        println!("Iterasi {}", iterasi);
        std::thread::sleep(std::time::Duration::from_millis(500));
    }

    // Cleanup before exiting
    println!("Cleanup... selesai setelah {} iterasi.", iterasi);
    // Resource, database connection, etc. cleaned here
}

Graceful Shutdown with Drop #

The RAII pattern ensures that cleanup always occurs, even when the program exits due to a panic or signal:

struct KoneksiDatabase {
    url: String,
}

impl KoneksiDatabase {
    fn baru(url: &str) -> Self {
        println!("Koneksi database dibuka: {}", url);
        KoneksiDatabase { url: url.to_string() }
    }
}

impl Drop for KoneksiDatabase {
    fn drop(&mut self) {
        // Always called when Database Connection goes out of scope
        println!("Koneksi database ditutup: {}", self.url);
    }
}

fn main() {
    let _db = KoneksiDatabase::baru("postgres://localhost/mydb");

    // Do the work...
    println!("Program berjalan...");

    // Drop is called automatically here, even if there is panic
}
// Output:
// Database connection opened: postgres://localhost/mydb
// Program running...
// Database connection closed: postgres://localhost/mydb

Process Information #

use std::env;
use std::process;

fn main() {
    // PID of the current process
    println!("PID: {}", process::id());

    // Current directory
    let cwd = env::current_dir().unwrap();
    println!("CWD: {}", cwd.display());

    // Executable path
    let exe = env::current_exe().unwrap();
    println!("Executable: {}", exe.display());

    // Program name of args[0]
    let nama_program = env::args()
        .next()
        .and_then(|p| std::path::Path::new(&p)
            .file_name()
            .map(|n| n.to_string_lossy().into_owned()))
        .unwrap_or_else(|| "unknown".to_string());
    println!("Nama program: {}", nama_program);

    // All environment variables that start with the specified prefix
    let rust_vars: Vec<_> = env::vars()
        .filter(|(k, _)| k.starts_with("RUST"))
        .collect();
    println!("RUST* env vars: {:?}", rust_vars);
}

When to Use Command vs Alternative #

Gunakan std::process::Command jika:
  ✓ Perlu menjalankan program eksternal yang sudah ada
  ✓ Integrasi dengan tool CLI seperti git, docker, cargo
  ✓ Output dari program sudah cukup — tidak perlu parsing binary protocol
  ✓ Program hanya tersedia sebagai binary, bukan library

Pertimbangkan alternatif library Rust jika:
  ✗ Fungsi tersedia sebagai crate — lebih portable, tidak bergantung pada binary di PATH
  ✗ Perlu performa tinggi — subprocess overhead signifikan untuk operasi kecil
  ✗ Perlu cross-platform yang ketat — perintah shell berbeda di Windows dan Unix
  ✗ Perlu error handling yang detail — output stderr seringkali tidak terstruktur

Gunakan shell script biasa jika:
  ✗ Program hanya dijalankan di lingkungan Unix yang terkontrol
  ✗ Orkestrasi sederhana yang tidak butuh error handling kompleks
  ✗ Tim lebih familiar dengan shell daripada Rust untuk scripting
flowchart TD
    A[Perlu menjalankan program eksternal?] --> B{Ada crate Rust yang melakukan hal sama?}
    B -- Yes --> C[Use a crate — more portable and type-safe]
    B -- No --> D{Perlu kontrol penuh atas I/O?}

    D -- Yes --> E["Command with Stdio::piped()<br/>stdin + stdout + stderr"]
    D -- No --> F{Hanya perlu exit status?}

    F -- Yes --> G["Command::status()\nSederhana, tidak tangkap output"]
    F -- No --> H["Command::output()\nTangkap stdout dan stderr"]

    G --> I["Check status.success()"]
    H --> J[Check output.status + parse stdout]
    E --> K["spawn() + write stdin\n+ wait_with_output()"]

    style C fill:#e8f5e9
    style E fill:#e3f2fd
    style G fill:#e8f5e9
    style H fill:#e8f5e9

Summary #

  • main() -> Result<(), Box<dyn Error>> — the most idiomatic way to handle errors in binary. Rust automatically prints an error message and exits with code 1 when Err is returned.
  • process::exit(kode) — exits immediately without running the destructor. For the necessary cleanup, use the RAII pattern with Drop or return an error from main.
  • env::var returns Result — environment variable may be missing or not UTF-8. Use unwrap_or_else for default values or ? for error propagation.
  • Command::output() for output capture, Command::status() for exit code onlyoutput() buffers the entire stdout and stderr in memory; for large output, use spawn() + streaming.
  • Stdio::piped() for two-way interaction — send to child process stdin, read from its stdout. Remember to close stdin (with drop) to signal EOF.
  • spawn() for background process, status() for blockingspawn() returns Child which can be kill()ed or wait()ed later. Don’t forget to call wait() so there are no zombie processes.
  • Always handle Err from Command — a binary not in the PATH produces Err, not a non-zero exit code. Two error levels: failed spawn (binary does not exist) and exit code non-zero (binary exists but failed).
  • Use crate clap for argument parsing — manual parsing is not scalable. clap with derive API generates help text, validation, and correct types automatically.


← Previous: Time & Duration   Next: Net →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact