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:

bash
cargo new sapa
cd sapa
cargo add clap --features derive

Feature flag tersebut penting — tanpa derive, crate clap hanya mengekspos builder API manual. Manifest proyek:

toml
[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:

rust
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:

bash
cargo run -- Budi

Output yang Dihasilkan:

text
Halo, Budi!

Ulangi sapaan tiga kali dengan option --times:

bash
cargo run -- Budi --times 3

Output yang Dihasilkan:

text
Halo, Budi!
Halo, Budi!
Halo, Budi!

Bentuk pendek -t bekerja dengan nilai yang sama:

bash
cargo run -- Siti -t 2

Output yang Dihasilkan:

text
Halo, Siti!
Halo, Siti!

Mari kita bedah kode di atas:

  1. Baris 1 (use clap::Parser;): membawa trait Parser sekaligus derive macro Parser — keduanya sengaja berbagi nama agar satu import mengurus dua kebutuhan: implementasi dan pemanggilan Cli::parse().
  2. 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.
  3. Baris 4 (#[derive(Parser)]): derive macro yang menghasilkan seluruh mesin parsing — pembacaan env::args(), pencocokan teks dengan deklarasi field, pengisian struct, dan jalur error.
  4. Baris 5 (#[command(name = "sapa", version, about)]): pengatur tingkat program. name menentukan nama perintah pada baris Usage; version mengaktifkan flag -V/--version dengan angka dari field version di Cargo.toml; about memakai doc comment sebagai deskripsi.
  5. Baris 8 (nama: String): field tanpa attribute menjadi positional argument — diisi dari teks tanpa nama, ditulis begitu saja setelah nama program. Tipe String yang bukan Option menjadikan argument ini wajib.
  6. Baris 11 (#[arg(short, long, default_value_t = 1)]): attribute argument. short membuat flag pendek -t dari huruf pertama field, long membuat --times dari nama field, dan default_value_t = 1 mengisi nilai 1 ketika pengguna tidak menuliskan option.
  7. Baris 12 (times: u32): tipe numerik memerintahkan parser memvalidasi teks sebagai angka — ketik --times tiga dan clap menolaknya sebelum kode kita sempat jalan.
  8. Baris 16 (let cli = Cli::parse();): satu pemanggilan yang membaca seluruh argument, atau mencetak pesan error dan mengakhiri proses bila ada masalah. Versi Cli::try_parse() tersedia bila kita ingin menangani kegagalan parsing sendiri sebagai Result.
  9. Baris 17-19 (for _ in 0..cli.times): logika bisnis murni — semua nilai sudah bertipe benar di struct, tanpa parse() atau pemilahan teks.

Membaca Output Bantuan #

Teks bantuan dibangkitkan otomatis dari deklarasi yang sama. Panggil --help:

bash
cargo run -- --help

Output yang Dihasilkan:

text
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 version

Setiap 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:

bash
cargo run

Output yang Dihasilkan:

text
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.