Environment Variable dan Proses di Rust
Setiap program berjalan di dalam sebuah environment (lingkungan). Sistem operasi memberinya sekumpulan nilai teks yang punya nama, seperti PATH, HOME, atau PORT. Inilah yang disebut environment variable. Environment variable adalah cara sederhana untuk mengonfigurasi program tanpa mengubah kodenya: binary yang sama bisa memakai port 8080 di laptopmu dan port 80 di server.
Sebuah program juga hidup berdampingan dengan program lain. Program bisa selesai dengan exit code yang memberi tahu pemanggilnya “berhasil” atau “gagal”, dan program juga bisa menjalankan program lain. Di Rust, alat untuk semua ini ada di dua modul standar: std::env dan std::process. Tidak perlu crate tambahan.
Membaca variabel dengan env::var
Section titled “Membaca variabel dengan env::var”std::env::var menerima sebuah nama dan mengembalikan Result. Hasilnya Ok(String) kalau variabelnya ada, dan Err kalau tidak ada (atau bukan Unicode yang valid). Karena variabel yang tidak ada adalah situasi yang wajar, Rust mewajibkanmu menanganinya:
use std::env;
fn main() { // Nama yang hampir pasti tidak di-set di komputer mana pun. match env::var("RUSTTUTORIAL_API_KEY") { Ok(value) => println!("Ketemu: {value}"), Err(e) => println!("Tidak tersedia: {e}"), }
let port = env::var("RUSTTUTORIAL_PORT").unwrap_or(String::from("8080")); println!("Port: {port}");}Output:
Tidak tersedia: environment variable not foundPort: 8080“environment variable not found” adalah pesan bawaan Rust (artinya environment variable tidak ditemukan), jadi tetap berbahasa Inggris.
Pakai match kalau kamu ingin bereaksi terhadap kasus variabel yang tidak ada. Pakai .unwrap_or(default) kalau variabel yang tidak ada cukup berarti “pakai nilai default”.
Nilai default, angka, dan flag
Section titled “Nilai default, angka, dan flag”Environment variable selalu berupa teks. Untuk mendapatkan angka, kamu harus mem-parse-nya, dan parsing juga bisa gagal. Pola yang umum adalah: ubah Result menjadi Option dengan .ok(), coba parse, lalu pakai nilai default kalau ada yang salah:
use std::env;
fn main() { let workers: u32 = env::var("RUSTTUTORIAL_WORKERS") .ok() .and_then(|text| text.parse().ok()) .unwrap_or(4);
let debug = env::var("RUSTTUTORIAL_DEBUG").is_ok();
println!("workers = {workers}"); println!("debug = {debug}");}Output:
workers = 4debug = false.is_ok() adalah cara cepat untuk memperlakukan sebuah variabel sebagai flag nyala/mati: asalkan variabelnya di-set, flag-nya menyala.
var_os, vars, dan mengubah variabel
Section titled “var_os, vars, dan mengubah variabel”env::var_os mengembalikan Option<OsString>, bukan Result. Fungsi ini tidak pernah gagal karena byte yang aneh, jadi cocok untuk path. env::vars() memberimu semua variabel sebagai pasangan (nama, nilai).
env::set_var mengubah sebuah variabel untuk proses yang sedang berjalan. Perhatikan blok unsafe di sekitarnya:
use std::env;
fn main() { unsafe { env::set_var("MYAPP_COLOR", "blue"); env::set_var("MYAPP_SIZE", "large"); }
println!("{:?}", env::var_os("MYAPP_COLOR")); println!("{:?}", env::var_os("MYAPP_MISSING"));
let mut mine: Vec<(String, String)> = env::vars() .filter(|(key, _)| key.starts_with("MYAPP_")) .collect(); mine.sort(); // urutan env::vars() tidak dijamin
for (key, value) in &mine { println!("{key}={value}"); }}Output:
Some("blue")NoneMYAPP_COLOR=blueMYAPP_SIZE=largeKenapa set_var itu unsafe? Di banyak sistem operasi, environment adalah satu tabel bersama untuk seluruh proses. Kalau satu thread mengubahnya sementara thread lain (atau library C) sedang membacanya, program bisa crash. Rust tidak bisa memeriksa hal ini untukmu, jadi sejak edition 2024, set_var dan remove_var adalah fungsi unsafe. Di edition 2021, keduanya masih bisa di-compile tanpa unsafe, tapi menulis bloknya tetap berfungsi di kedua edisi. Aturan amannya: hanya ubah variabel di awal main, sebelum kamu menjalankan thread apa pun. Lebih baik lagi, jangan ubah environment milikmu sendiri sama sekali. Untuk memberikan variabel ke program anak (child), pakai Command::env (dibahas di bawah). Lihat Unsafe Rust untuk memahami arti unsafe.
Direktori saat ini
Section titled “Direktori saat ini”env::current_dir() mengembalikan folder tempat program dijalankan, sebagai PathBuf. Path relatif seperti "config.toml" dihitung dari folder ini. Path aslinya bergantung pada komputermu, jadi contoh ini hanya mencetak hal-hal yang benar di mana saja:
use std::env;
fn main() { let dir = env::current_dir().expect("tidak bisa membaca direktori saat ini"); println!("Apakah path-nya absolut? {}", dir.is_absolute());
let config = dir.join("config.toml"); println!("Berakhiran config.toml? {}", config.ends_with("config.toml"));}Output:
Apakah path-nya absolut? trueBerakhiran config.toml? trueCetak dir.display() di komputermu sendiri untuk melihat path lengkapnya. Argumen command line juga ada di std::env; topik itu punya halaman sendiri: Argumen command line.
Exit code dengan process::exit
Section titled “Exit code dengan process::exit”Saat program selesai, ia mengembalikan sebuah angka kepada siapa pun yang menjalankannya. 0 berarti berhasil; angka lain berarti gagal. Skrip shell dan sistem CI memeriksa angka ini. std::process::exit(code) langsung menghentikan program dengan kode tersebut:
use std::env;use std::process;
fn main() { println!("Memulai...");
let token = match env::var("RUSTTUTORIAL_TOKEN") { Ok(t) => t, Err(_) => { eprintln!("error: RUSTTUTORIAL_TOKEN belum di-set"); process::exit(1); } };
println!("Memakai token {token}");}Output:
Memulai...Baris error dicetak dengan eprintln!, jadi masuknya ke stderr, bukan stdout. Playground menampilkannya di area terpisah, bersama dengan exit status-nya.
Ada dua hal yang perlu kamu tahu. Pertama, process::exit langsung mengakhiri program, jadi destructor (Drop) untuk nilai-nilai yang masih hidup tidak dijalankan. Kedua, sering kali kamu tidak membutuhkannya: kalau main mengembalikan Result<(), E> dan kamu mengembalikan Err, Rust akan mencetak error-nya dan keluar dengan kode 1 untukmu.
Menjalankan program lain dengan Command
Section titled “Menjalankan program lain dengan Command”std::process::Command menjalankan program lain. Kamu menyusunnya langkah demi langkah: nama program, lalu setiap argumen dengan .arg() atau .args(). Setelah itu, pilih cara menjalankannya:
.output()menunggu program selesai dan menangkap stdout dan stderr-nya..status()juga menunggu, tapi membiarkan output-nya langsung tampil di terminalmu.
use std::process::Command;
fn main() { let output = Command::new("echo") .arg("Halo") .arg("dari proses anak") .output() .expect("gagal menjalankan echo");
let text = String::from_utf8_lossy(&output.stdout); println!("berhasil: {}", output.status.success()); println!("stdout: {}", text.trim());}Output:
berhasil: truestdout: Halo dari proses anakoutput.stdout berupa byte mentah (Vec<u8>), karena sebuah program bisa mencetak apa saja. String::from_utf8_lossy mengubahnya menjadi teks.
Berikut contoh .status() untuk membaca exit code, dan .env() untuk mengirim variabel hanya ke program anak:
use std::process::Command;
fn main() { let status = Command::new("sh") .args(["-c", "exit 3"]) .status() .expect("gagal menjalankan sh");
println!("berhasil: {}", status.success()); println!("kode: {:?}", status.code());
let output = Command::new("sh") .args(["-c", "echo \"$GREETING, $NAME\""]) .env("GREETING", "Hai") .env("NAME", "Ferris") .output() .expect("gagal menjalankan sh"); print!("{}", String::from_utf8_lossy(&output.stdout));}Output:
berhasil: falsekode: Some(3)Hai, FerrisDi Windows: echo bukan program terpisah; perintah itu sudah tertanam di shell cmd, dan sh biasanya tidak ada. Ide yang sama kira-kira ditulis seperti Command::new("cmd").args(["/C", "echo Halo"]). Contoh-contoh di atas berjalan di Linux, macOS, dan Rust Playground.
Menggabungkan semuanya: konfigurasi dari environment
Section titled “Menggabungkan semuanya: konfigurasi dari environment”Program sungguhan biasanya membaca semua pengaturannya sekali saja, saat mulai berjalan, ke dalam sebuah struct. Setiap pengaturan punya nilai default yang masuk akal, dan nilai yang salah menghasilkan pesan yang jelas, bukan crash:
use std::env;
#[derive(Debug)]struct Config { host: String, port: u16, verbose: bool,}
impl Config { fn from_env() -> Config { let host = env::var("APP_HOST").unwrap_or_else(|_| String::from("127.0.0.1"));
let port = match env::var("APP_PORT") { Ok(text) => text.parse().unwrap_or_else(|_| { println!("APP_PORT={text:?} bukan angka, memakai 3000"); 3000 }), Err(_) => 3000, };
let verbose = matches!(env::var("APP_VERBOSE").as_deref(), Ok("1") | Ok("true"));
Config { host, port, verbose } }}
fn main() { // Simulasikan apa yang mungkin di-set pengguna sebelum menjalankan program. unsafe { env::set_var("APP_PORT", "delapan puluh"); env::set_var("APP_VERBOSE", "true"); }
let config = Config::from_env(); println!("{config:?}");}Output:
APP_PORT="delapan puluh" bukan angka, memakai 3000Config { host: "127.0.0.1", port: 3000, verbose: true }Dalam pemakaian sungguhan, hapus baris-baris set_var dan set variabelnya di shell kamu:
APP_PORT=9000 APP_VERBOSE=1 cargo run(Di Windows PowerShell: $env:APP_PORT="9000"; cargo run.)
Kesalahan umum
Section titled “Kesalahan umum”Memperlakukan env::var seolah-olah mengembalikan String. Fungsi ini mengembalikan Result, karena variabelnya mungkin tidak ada:
let port: String = env::var("PORT");error[E0308]: mismatched types --> src/main.rs:4:24 |4 | let port: String = env::var("PORT"); | ------ ^^^^^^^^^^^^^^^^ expected `String`, found `Result<String, VarError>` | | | expected due to thisCompiler bilang tipenya tidak cocok: yang diharapkan String, tapi yang didapat Result<String, VarError>. Solusinya: tentukan apa arti variabel yang tidak ada. Pakai env::var("PORT").unwrap_or(String::from("8080")) untuk nilai default, atau match untuk melaporkannya.
Memanggil set_var tanpa unsafe di edition 2024. Proyek baru yang dibuat dengan cargo new memakai edition 2024, jadi kode ini gagal:
error[E0133]: call to unsafe function `set_var` is unsafe and requires unsafe block --> src/main.rs:2:5 |2 | std::env::set_var("MODE", "fast"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ call to unsafe functionArtinya, memanggil fungsi unsafe set_var membutuhkan blok unsafe. Solusinya: bungkus dengan unsafe { ... } dan panggil hanya sebelum ada thread yang berjalan, atau pakai Command::env untuk program anak.
Menaruh seluruh baris perintah di Command::new. Command::new("echo hello") mencari program yang namanya persis echo hello, yang tentu saja tidak ada. .output() akan mengembalikan Err(Os { code: 2, kind: NotFound, message: "No such file or directory" }) (file atau direktori tidak ditemukan). Solusinya: nama program ditaruh di new, dan setiap argumen ditaruh di .arg() atau .args(): Command::new("echo").arg("hello").
Ringkasan
Section titled “Ringkasan”env::var("NAME")mengembalikanResult<String, VarError>. Tangani kasus variabel yang tidak ada denganmatch,.unwrap_or(), atau.ok().- Nilai environment berupa teks. Parse sendiri angkanya, dan pakai nilai default kalau parsing gagal.
env::set_varbersifatunsafedi edition 2024 karena environment dipakai bersama oleh semua thread. UtamakanCommand::envuntuk program anak.process::exit(code)langsung mengakhiri program dengan sebuah exit code:0berarti berhasil, angka lain berarti gagal.Command::new("program").args([...])menjalankan program lain..output()menangkap apa yang dicetaknya;.status()memberimu exit code-nya.
Kuis singkat
Section titled “Kuis singkat”Kuis singkat
1. Apa yang dikembalikan env::var("PORT") kalau PORT tidak di-set?
2. Kenapa env::set_var menjadi fungsi unsafe di edition 2024?
3. Exit code mana yang secara tradisional berarti "berhasil"?
4. Bagaimana cara menjalankan ls -l dengan Command?
Skor: 0 / 4
Bacaan lebih lanjut
Section titled “Bacaan lebih lanjut”- std::env — Rust standard library (bahasa Inggris): semua fungsi untuk variabel, argumen, dan direktori.
- Working with Environment Variables — The Rust Book (bahasa Inggris): proyek di Book yang membaca sebuah pengaturan dari environment.
- std::process::Command (bahasa Inggris): semua opsi untuk menjalankan program, dengan contoh untuk Windows dan Unix.
- Newly unsafe functions — Edition Guide (bahasa Inggris): penjelasan resmi kenapa
set_varmenjadiunsafe. - Child processes — Rust by Example (bahasa Inggris): contoh-contoh singkat lainnya untuk menjalankan program dan memakai pipe.