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 crateclap. Hampir semua CLI tool Rust di production menggunakanclapkarena 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:#e8f5e9Ringkasan #
main() -> Result<(), Box<dyn Error>>— cara paling idiomatik untuk menangani error di binary. Rust otomatis mencetak pesan error dan keluar dengan kode 1 saatErrdikembalikan.process::exit(kode)— keluar segera tanpa menjalankan destruktor. Untuk cleanup yang diperlukan, gunakan pola RAII denganDropatau kembalikan error darimain.env::varmengembalikanResult— environment variable mungkin tidak ada atau bukan UTF-8. Gunakanunwrap_or_elseuntuk nilai default atau?untuk propagasi error.Command::output()untuk tangkap output,Command::status()untuk hanya exit code —output()buffer seluruh stdout dan stderr di memori; untuk output besar, gunakanspawn()+ streaming.Stdio::piped()untuk interaksi dua arah — kirim kestdinproses anak, baca daristdout-nya. Ingat menutupstdin(dengandrop) untuk menandakan EOF.spawn()untuk background process,status()untuk blocking —spawn()mengembalikanChildyang bisa di-kill()atau di-wait()nanti. Jangan lupa callwait()agar tidak ada zombie process.- Selalu tangani
ErrdariCommand— binary yang tidak ada di PATH menghasilkanErr, bukan exit code non-zero. Dua level error: gagal spawn (binary tidak ada) dan exit code non-zero (binary ada tapi gagal).- Gunakan crate
clapuntuk parsing argumen — parsing manual tidak scalable.clapdengan derive API menghasilkan help text, validasi, dan tipe yang tepat secara otomatis.