Aplikasi baris perintah adalah wilayah kedua terpopuler proyek Rust setelah library — dan pintu masuk setiap aplikasi CLI adalah deretan teks mentah dari std::env::args. Menerjemahkan teks mentah itu sendiri terasa mudah di awal: satu positional argument, satu if, selesai. Persoalan muncul begitu program tumbuh — pengguna mengetik --times=3 alih-alih --times 3, bertanya --help, mengetik --time yang salah eja, atau lupa mengisi argument wajib — dan parser buatan sendiri harus menangani semua cabang itu dengan pesan error yang layak. Crate clap menyelesaikan seluruh kelas masalah tersebut: parser argument, pembangkit teks bantuan, validasi tipe, sampai penanganan --version, semuanya dari satu deklarasi struct. Pada Chapter ini kita membangun program CLI lengkap dengan clap, membaca teks bantuan yang dihasilkannya, dan mengamati perilakunya ketika pengguna salah memanggil program.
Harga Parsing Argumen Manual #
Mari kita perinci mengapa parser manual cepat menjadi beban. std::env::args() mengembalikan iterator String mentah — tidak ada perbedaan bentuk antara positional argument, flag boolean, dan option bernilai; kita yang memilah sendiri. Program kecil dengan dua argument mungkin hanya butuh sepuluh baris pemilahan, tetapi setiap kemampuan baru menambah kewajiban: mendeteksi format --times=3, membedakan flag -t dari nilai 3, memvalidasi bahwa teks --times benar-benar angka, menolak argument tak dikenal, dan mencetak panduan pemakaian yang selalu mutakhir dengan kode.
Terberat dari semuanya adalah konsistensi: help text yang kita tulis manual mudah melenceng dari perilaku kode, dan pesan error yang kita rangkai sendiri jarang menyebut format pemakaian yang benar. Parser deklaratif memutus masalah konsistensi itu dari akarnya — help text, pesan error, dan logika parsing lahir dari sumber yang sama, sehingga mustahil saling bertentangan. Itulah alasan di balik dominasi clap: crate yang sama dipakai oleh cargo sendiri, rustup, dan ribuan alat baris perintah lain di ekosistem.
Mekanisme Derive API #
clap menawarkan beberapa gaya API, dan gaya paling populer saat ini adalah derive API yang kita aktifkan lewat feature derive. Gaya ini memakai macro #[derive(Parser)] pada sebuah struct: setiap field struct menjadi satu argument, tipe field menentukan cara parsing, dan attribute mengatur detail seperti nama flag dan nilai bawaan. Kode yang dihasilkan derive inilah — bukan kode kita — yang membaca env::args(), mencocokkan teks dengan deklarasi, mengisi struct, atau mencetak error dan menghentikan proses bila pencocokan gagal.
Tiga sumber deklarasi bekerja serempak di derive API. Doc comment /// pada struct dan field menjadi teks bantuan; attribute #[command(...)] mengatur perilaku program secara keseluruhan seperti nama perintah dan flag --version; attribute #[arg(...)] mengatur argument seperti bentuk pendek, bentuk panjang, dan nilai bawaan. Hasil akhirnya: struct yang terisi penuh dengan tipe sudah benar — String untuk nama, u32 untuk jumlah — siap dipakai logika bisnis tanpa satu baris parsing manual.
Membangun Program sapa #
Program kita bernama sapa: menerima satu nama dan opsi --times untuk mengulang sapaan. Buat proyek dan tambahkan clap dengan feature derive:
cargo new sapa
cd sapa
cargo add clap --features deriveFeature flag tersebut penting — tanpa derive, crate clap hanya mengekspos builder API manual. Manifest proyek:
[package]
name = "sapa"
version = "0.1.0"
edition = "2024"
[dependencies]
clap = { version = "4.6.7", features = ["derive"] }Seluruh deklarasi CLI hidup di src/main.rs:
use clap::Parser;
/// Program sapaan untuk berlatih crate clap.
#[derive(Parser)]
#[command(name = "sapa", version, about)]
struct Cli {
/// Nama orang yang akan disapa.
nama: String,
/// Berapa kali sapaan diulang.
#[arg(short, long, default_value_t = 1)]
times: u32,
}
fn main() {
let cli = Cli::parse();
for _ in 0..cli.times {
println!("Halo, {}!", cli.nama);
}
}Jalankan program dengan satu nama:
cargo run -- BudiOutput yang Dihasilkan:
Halo, Budi!Ulangi sapaan tiga kali dengan option --times:
cargo run -- Budi --times 3Output yang Dihasilkan:
Halo, Budi!
Halo, Budi!
Halo, Budi!Bentuk pendek -t bekerja dengan nilai yang sama:
cargo run -- Siti -t 2Output yang Dihasilkan:
Halo, Siti!
Halo, Siti!Mari kita bedah kode di atas:
- Baris 1 (
use clap::Parser;): membawa traitParsersekaligus derive macroParser— keduanya sengaja berbagi nama agar satu import mengurus dua kebutuhan: implementasi dan pemanggilanCli::parse(). - Baris 3 (
/// Program sapaan untuk berlatih crate clap.): doc comment pada struct menjadi teks deskripsi program di output bantuan. Kebiasaan dokumentasi dari Section Dokumentasi langsung terbawa ke CLI. - Baris 4 (
#[derive(Parser)]): derive macro yang menghasilkan seluruh mesin parsing — pembacaanenv::args(), pencocokan teks dengan deklarasi field, pengisian struct, dan jalur error. - Baris 5 (
#[command(name = "sapa", version, about)]): pengatur tingkat program.namemenentukan nama perintah pada barisUsage;versionmengaktifkan flag-V/--versiondengan angka dari fieldversiondiCargo.toml;aboutmemakai doc comment sebagai deskripsi. - Baris 8 (
nama: String): field tanpa attribute menjadi positional argument — diisi dari teks tanpa nama, ditulis begitu saja setelah nama program. TipeStringyang bukanOptionmenjadikan argument ini wajib. - Baris 11 (
#[arg(short, long, default_value_t = 1)]): attribute argument.shortmembuat flag pendek-tdari huruf pertama field,longmembuat--timesdari nama field, dandefault_value_t = 1mengisi nilai1ketika pengguna tidak menuliskan option. - Baris 12 (
times: u32): tipe numerik memerintahkan parser memvalidasi teks sebagai angka — ketik--times tigadan clap menolaknya sebelum kode kita sempat jalan. - Baris 16 (
let cli = Cli::parse();): satu pemanggilan yang membaca seluruh argument, atau mencetak pesan error dan mengakhiri proses bila ada masalah. VersiCli::try_parse()tersedia bila kita ingin menangani kegagalan parsing sendiri sebagaiResult. - Baris 17-19 (
for _ in 0..cli.times): logika bisnis murni — semua nilai sudah bertipe benar di struct, tanpaparse()atau pemilahan teks.
Membaca Output Bantuan #
Teks bantuan dibangkitkan otomatis dari deklarasi yang sama. Panggil --help:
cargo run -- --helpOutput yang Dihasilkan:
Program sapaan untuk berlatih crate clap
Usage: sapa [OPTIONS] <NAMA>
Arguments:
<NAMA> Nama orang yang akan disapa
Options:
-t, --times <TIMES> Berapa kali sapaan diulang [default: 1]
-h, --help Print help
-V, --version Print versionSetiap baris pada output tersebut dapat kita telusuri balik ke deklarasi. Deskripsi baris pertama adalah doc comment struct; baris Usage memakai nama sapa dari #[command(name = ...)]; [OPTIONS] hadir karena program punya option opsional, <NAMA> bersudut karena wajib; deskripsi Nama orang yang akan disapa berasal dari doc comment field nama; [default: 1] dari default_value_t; dan dua flag terakhir disisipkan clap sendiri. Menambahkan field baru pada struct otomatis menambah baris di sini — help text tidak mungkin kedaluwarsa karena tidak pernah ditulis tangan.
Pemanggilan cargo run -- --version mencetak sapa 0.1.0 — angka 0.1.0 diambil langsung dari field version di Cargo.toml, sehingga nomor versi program selalu sinkron dengan nomor versi paket. Konvensi penamaan juga diurus otomatis: nama field multi kata seperti max_depth akan dipublikasikan sebagai --max-depth dalam bentuk kebab-case, mengikuti kebiasaan alat CLI di dunia Unix.
Perilaku Saat Argumen Salah #
Bagian yang paling membedakan parser matang dari parser improvisasi adalah kegagalan. Panggil program tanpa argument sama sekali:
cargo runOutput yang Dihasilkan:
error: the following required arguments were not provided:
<NAMA>
Usage: sapa <NAMA>
For more information, try '--help'.Pesan tersebut disusun clap, bukan kode kita: jenis kesalahan, argument yang kurang, format pemakaian yang benar, dan petunjuk menuju bantuan. Proses berakhir dengan exit status 2 — konvensi Unix untuk kesalahan pemakaian — sehingga script shell yang memanggil program kita bisa mendeteksi kegagalan lewat kode status tanpa memilah teks. Kesalahan bentuk lain ditangani dengan mutu yang sama: nilai --times abc ditolak karena bukan angka, option tak dikenal seperti --time memunculkan usulan ejaan terdekat, dan --help yang ditempatkan di posisi mana pun tetap dimengerti.
Ringkasan #
Derive API clap mengubah deklarasi struct menjadi parser CLI lengkap: field menjadi argument bertipe, doc comment menjadi help text, attribute #[command] dan #[arg] mengatur nama flag dan nilai bawaan, dan Cli::parse() mengerjakan sisanya — termasuk pesan error bermutu dan exit status 2 untuk kesalahan pemakaian. Seluruh teks bantuan dan pesan galat lahir dari satu sumber deklarasi, sehingga dokumentasi program tidak mungkin melenceng dari perilakunya. Dua Chapter teknis terakhir di Section ini menghadirkan dua crate komunitas lain yang nyaris selalu hadir dalam proyek nyata: rand untuk pengacakan dan chrono untuk tanggal dan waktu.