Process

Process #

Setiap program yang berjalan adalah sebuah proses. Rust menyediakan std::process untuk berinteraksi dengan proses — baik proses itu sendiri maupun proses anak yang diluncurkan. Kebutuhan ini muncul di banyak skenario: menjalankan perintah shell dari dalam program, membangun CLI tool yang memanggil tool lain, membaca konfigurasi dari environment variables, atau mengontrol bagaimana program keluar saat terjadi error. std::process juga mencakup akses ke argumen baris perintah dan environment variables yang membentuk konteks eksekusi program. Artikel ini membahas semua aspek tersebut secara komprehensif — dari peluncuran proses anak sederhana hingga pola pipeline yang lebih kompleks.

std::process::exit dan Kode Keluar #

Cara paling mendasar berinteraksi dengan proses adalah mengontrol bagaimana ia keluar. Di Rust, main() yang mengembalikan () selalu keluar dengan kode 0 (sukses). Untuk keluar dengan kode lain, gunakan 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);  // keluar dengan kode error
    }

    println!("Halo, {}!", args[1]);
    // keluar dengan kode 0 secara implisit
}

Cara yang lebih idiomatik adalah menggunakan main() yang mengembalikan Result — ini memungkinkan penggunaan operator ? di seluruh fungsi main:

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(())
}
// Jika error terjadi, Rust mencetak pesan error dan keluar dengan kode 1
// Jika Ok(()), keluar dengan kode 0

Untuk kontrol yang lebih eksplisit, implementasikan pola “main yang memanggil run”:

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);
    }
}

Konvensi Kode Keluar #

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;

// Definisikan konstanta untuk kode keluar yang bermakna
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(())
}

Argumen Baris Perintah #

std::env::args() memberikan akses ke argumen yang diberikan saat program dijalankan.

use std::env;

fn main() {
    // args() mengembalikan iterator — elemen pertama adalah nama program
    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);
    }

    // Parsing argumen sederhana
    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 nama program
    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);
    }
}
Untuk parsing argumen baris perintah yang lebih kompleks — subcommand, tipe argumen, validasi, dan help text otomatis — gunakan crate clap. Hampir semua CLI tool Rust di production menggunakan clap karena parsing manual cepat menjadi tidak maintainable untuk program yang lebih dari beberapa flag.
[dependencies]
clap = { version = "4", features = ["derive"] }
use clap::Parser;

#[derive(Parser, Debug)]
#[command(name = "myapp", about = "Contoh aplikasi CLI")]
struct Args {
    /// File input yang akan diproses
    #[arg(required = true)]
    input: Vec<String>,

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

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

    /// Jumlah thread worker
    #[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 adalah cara umum untuk mengkonfigurasi program tanpa mengubah kode atau file konfigurasi.

use std::env;

fn main() {
    // Membaca satu 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),
    }

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

    // Parse langsung ke tipe yang diinginkan
    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);

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

    // Mengubah environment variable untuk proses ini
    // (hanya berlaku untuk proses saat ini, tidak mempengaruhi parent)
    env::set_var("MY_APP_ENV", "production");
    println!("{}", env::var("MY_APP_ENV").unwrap());  // "production"

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

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

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

Konfigurasi Berbasis Environment Variables #

Pola yang sangat umum di aplikasi produksi adalah membaca semua konfigurasi dari environment variables saat 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() {
    // Muat konfigurasi saat 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 — Menjalankan Proses Anak #

std::process::Command adalah API untuk meluncurkan dan berinteraksi dengan proses anak.

use std::process::Command;

fn main() {
    // Menjalankan perintah sederhana
    let status = Command::new("echo")
        .arg("Halo dari proses anak!")
        .status()  // jalankan dan tunggu, kembalikan exit status
        .expect("Gagal menjalankan echo");

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

    // Perintah dengan banyak argumen
    let status = Command::new("ls")
        .args(["-la", "/tmp"])
        .status()
        .expect("Gagal menjalankan ls");

    // Menangkap output — output tidak ditampilkan ke terminal
    let output = Command::new("date")
        .output()  // jalankan dan tangkap 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: menggunakan shell untuk perintah sederhana — overhead tidak perlu
    let output = Command::new("sh")
        .arg("-c")
        .arg("echo hello")
        .output()
        .unwrap();

    // BENAR: panggil binary langsung jika memungkinkan
    let output = Command::new("echo")
        .arg("hello")
        .output()
        .unwrap();
    println!("{}", String::from_utf8_lossy(&output.stdout).trim());
}

Menangani Output dan Error #

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),
    }

    // Memeriksa apakah program tersedia di 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 dan Working Directory untuk Proses Anak #

Command memungkinkan konfigurasi environment dan direktori kerja proses anak secara granular.

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

fn main() {
    // Setel environment variable untuk proses anak saja
    let output = Command::new("printenv")
        .arg("MY_VAR")
        .env("MY_VAR", "nilai_dari_parent")
        .output()
        .unwrap();
    println!("{}", String::from_utf8_lossy(&output.stdout).trim());

    // Hapus environment variable tertentu dari proses anak
    let output = Command::new("printenv")
        .arg("HOME")
        .env_remove("HOME")
        .output()
        .unwrap();
    println!("HOME di anak: '{}'", String::from_utf8_lossy(&output.stdout).trim());

    // Bersihkan semua env vars, setel hanya yang diperlukan — untuk proses yang terisolasi
    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));

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

    // Kombinasi: jalankan cargo di direktori proyek tertentu
    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 dan stdout #

Untuk interaksi dua arah dengan proses anak — mengirim input dan membaca output — gunakan Stdio::piped().

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

fn main() {
    // Kirim input ke stdin proses anak
    let mut child = Command::new("cat")  // cat membaca stdin dan mencetak ke stdout
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .spawn()  // spawn menjalankan tanpa menunggu
        .expect("Gagal spawn cat");

    // Kirim data ke stdin proses anak
    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 akan di-close saat ter-drop — menandakan EOF ke proses anak

    // Tunggu proses selesai dan ambil 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 satu proses ke input proses lain
    // Ekuivalen dengan: echo "halo dunia rust" | 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());
    // "HALO DUNIA RUST"
}

Menjalankan Proses di Background #

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

fn main() {
    // spawn() mengembalikan Child — proses berjalan di background
    let mut child = Command::new("sleep")
        .arg("10")
        .spawn()
        .expect("Gagal spawn sleep");

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

    // Lakukan pekerjaan lain sementara proses berjalan
    thread::sleep(Duration::from_millis(100));

    // Cek apakah proses masih berjalan
    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),
    }

    // Hentikan proses secara paksa
    child.kill().expect("Gagal membunuh proses");
    let status = child.wait().expect("Gagal tunggu setelah kill");
    println!("Proses dihentikan: {:?}", status);

    // Timeout: tunggu maksimal N detik, lalu 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 setelah 100ms"
    }
}

Pola Shell Scripting dalam Rust #

Rust sering digunakan sebagai pengganti shell script untuk tugas-tugas yang membutuhkan keandalan lebih tinggi. Berikut pola-pola yang umum.

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

// Helper untuk menjalankan perintah seperti di 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())
    }
}

// Lebih aman: jalankan binary langsung tanpa 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>> {
    // Cek dependensi yang dibutuhkan
    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);
            }
        }
    }

    // Ambil informasi git
    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 proyek
    println!("\nMemulai build...");
    let output = Command::new("cargo")
        .args(["build", "--release"])
        .status()?;

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

    // Operasi file yang lebih aman dari shell
    let versi = "1.0.0";
    let nama_arsip = format!("release-v{}.tar.gz", versi);

    // Buat direktori jika belum ada
    fs::create_dir_all("dist")?;

    // Salin 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(())
}

Menangkap Sinyal Proses #

Rust standard library tidak menyediakan penanganan sinyal secara langsung karena kompleksitas platform. Untuk kebutuhan ini, crate ctrlc (untuk Ctrl+C) atau signal-hook (untuk sinyal lengkap) adalah pilihan standar.

[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);

    // Tangkap 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.");

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

    // Cleanup sebelum keluar
    println!("Cleanup... selesai setelah {} iterasi.", iterasi);
    // Resource, koneksi database, dll. dibersihkan di sini
}

Graceful Shutdown dengan Drop #

Pola RAII memastikan cleanup selalu terjadi, bahkan saat program keluar karena panic atau sinyal:

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) {
        // Selalu dipanggil saat KoneksiDatabase keluar scope
        println!("Koneksi database ditutup: {}", self.url);
    }
}

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

    // Lakukan pekerjaan...
    println!("Program berjalan...");

    // Drop dipanggil otomatis di sini, bahkan jika ada panic
}
// Output:
// Koneksi database dibuka: postgres://localhost/mydb
// Program berjalan...
// Koneksi database ditutup: postgres://localhost/mydb

Informasi Proses #

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

fn main() {
    // PID proses saat ini
    println!("PID: {}", process::id());

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

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

    // Nama program dari 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);

    // Semua environment variables yang dimulai dengan prefix tertentu
    let rust_vars: Vec<_> = env::vars()
        .filter(|(k, _)| k.starts_with("RUST"))
        .collect();
    println!("RUST* env vars: {:?}", rust_vars);
}

Kapan Menggunakan Command vs Alternatif #

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 -- Ya --> C["Gunakan crate — lebih portable dan type-safe"]
    B -- Tidak --> D{Perlu kontrol penuh atas I/O?}

    D -- Ya --> E["Command dengan Stdio::piped()\nstdin + stdout + stderr"]
    D -- Tidak --> F{Hanya perlu exit status?}

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

    G --> I[Periksa status.success()]
    H --> J[Periksa 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

Ringkasan #

  • main() -> Result<(), Box<dyn Error>> — cara paling idiomatik untuk menangani error di binary. Rust otomatis mencetak pesan error dan keluar dengan kode 1 saat Err dikembalikan.
  • process::exit(kode) — keluar segera tanpa menjalankan destruktor. Untuk cleanup yang diperlukan, gunakan pola RAII dengan Drop atau kembalikan error dari main.
  • env::var mengembalikan Result — environment variable mungkin tidak ada atau bukan UTF-8. Gunakan unwrap_or_else untuk nilai default atau ? untuk propagasi error.
  • Command::output() untuk tangkap output, Command::status() untuk hanya exit codeoutput() buffer seluruh stdout dan stderr di memori; untuk output besar, gunakan spawn() + streaming.
  • Stdio::piped() untuk interaksi dua arah — kirim ke stdin proses anak, baca dari stdout-nya. Ingat menutup stdin (dengan drop) untuk menandakan EOF.
  • spawn() untuk background process, status() untuk blockingspawn() mengembalikan Child yang bisa di-kill() atau di-wait() nanti. Jangan lupa call wait() agar tidak ada zombie process.
  • Selalu tangani Err dari Command — binary yang tidak ada di PATH menghasilkan Err, bukan exit code non-zero. Dua level error: gagal spawn (binary tidak ada) dan exit code non-zero (binary ada tapi gagal).
  • Gunakan crate clap untuk parsing argumen — parsing manual tidak scalable. clap dengan derive API menghasilkan help text, validasi, dan tipe yang tepat secara otomatis.

← Sebelumnya: Time & Duration   Berikutnya: Net →

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