Pada titik ini kita sudah memegang seluruh peralatan dasar Rust: ownership mengatur kepemilikan data, trait menyusun perilaku lintas tipe, generic dan newtype memberi kita cara memperketat tipe, dan Result mengemas kegagalan dengan rapi. Peralatan tersebut menyelesaikan masalah di dalam kode kita sendiri. Section ini mengalihkan perhatian ke arah yang berbeda: bagaimana kode kita dipakai orang lain. Setiap tanda tangan publik yang kita terbitkan — fungsi, method, struct, tipe error — adalah API, dan kualitas API menentukan apakah pemanggil berhasil dalam hitungan menit atau tersesat berhari-hari. Standar yang kita pegang sepanjang section ini ringkasnya satu kalimat: API yang baik mudah dipakai dan sulit disalahgunakan. Prinsip tersebut bukan gagasan baru, tetapi Rust memberi kita alat yang tidak dimiliki banyak bahasa lain — compiler yang bersedia menjadi penjaga kontrak.
API sebagai Kontrak #
API adalah seluruh bagian kode kita yang terlihat dari luar: nama fungsi, urutan dan tipe parameter, tipe kembalian, beserta dokumentasinya. Pemanggil tidak membaca isi fungsi kita; pemanggil hanya membaca tanda tangan. Ketika tanda tangan berbunyi fn transfer(amount: u64, recipient: u64), seluruh kontrak yang tersedia bagi pemanggil hanyalah “kirim dua angka”. Aturan tambahan seperti “amount tidak boleh nol” atau “recipient harus sudah terdaftar” tidak terlihat pada tanda tangan tersebut, sehingga aturan tersebut hanya bisa dilanggar, bukan dicegah.
Kesalahan pemakaian yang berulang-ulang bukan sepenuhnya kesalahan pemanggil. Ketika sepuluh orang melakukan kesalahan yang sama pada library yang sama, pola pemakaian yang salah itu adalah celah yang dibiarkan terbuka oleh API-nya. Desain API yang baik mempersempit celah sebelum kode sempat berjalan, dan Rust menyediakan mekanisme untuk mempersempit celah pada waktu kompilasi — bukan pada saat insiden terjadi di produksi.
Prinsip Pertama: Mudah Dipakai #
Masalah paling klasik pada API yang tidak enak dipakai adalah parameter posisi yang tidak menjelaskan dirinya sendiri. Empat parameter dengan tipe &str, bool, bool, bool membuat pemanggil harus menghafal urutan dan makna setiap argumen. Kompilator hanya memeriksa tipe, bukan makna, sehingga dua bool yang tertukar tempat tetap lolos pemeriksaan tipe. Mari kita lihat dua versi API pembuatan akun dalam satu program:
// API versi A: parameter bebas, urutan boolean rawan tertukar
fn create_account_v1(name: &str, admin: bool, active: bool, verified: bool) -> String {
format!(
"Akun {} (admin: {}, aktif: {}, terverifikasi: {})",
name, admin, active, verified
)
}
// Opsi bernama untuk API versi B
struct AccountOptions {
admin: bool,
active: bool,
verified: bool,
}
// API versi B: pilihan pemanggil dibaca sebagai nama, bukan posisi
fn create_account_v2(name: &str, options: AccountOptions) -> String {
format!(
"Akun {} (admin: {}, aktif: {}, terverifikasi: {})",
name, options.admin, options.active, options.verified
)
}
fn main() {
let akun_a = create_account_v1("Budi", true, false, true);
println!("{}", akun_a);
let akun_b = create_account_v2(
"Siti",
AccountOptions { admin: false, active: true, verified: true },
);
println!("{}", akun_b);
}Output yang Dihasilkan:
Akun Budi (admin: true, aktif: false, terverifikasi: true)
Akun Siti (admin: false, aktif: true, terverifikasi: true)Mari kita bedah kode di atas:
- Baris 2 (
fn create_account_v1(name: &str, admin: bool, active: bool, verified: bool)): tanda tangan versi A menerima empat argumen posisi. Kompilator hanya memastikan argumen pertama bertipe&strdan tiga sisanya bertipebool; kompilator tidak tahu argumenboolmana yang bermakna admin dan mana yang bermakna terverifikasi. - Baris 25 (
create_account_v1("Budi", true, false, true)): pemanggil mengirim tigaboolberurutan. Nilaifalsedi tengah bisa berarti akun tidak aktif atau tidak terverifikasi — maknanya hanya diketahui pembuat API dan orang yang menghafal dokumentasinya. - Baris 10-14 (
struct AccountOptions { ... }): versi B mengangkat tiga pilihan tersebut menjadi field struct. Setiap field wajib dituliskan namanya saat konstruksi, jadi makna setiap nilai dibaca langsung dari kode, bukan dari ingatan. - Baris 30 (
AccountOptions { admin: false, active: true, verified: true }): pemanggil versi B tidak mungkin menukar dua nilai tanpa menukarkan namanya sekaligus. Bentuk pemanggilan seperti ini juga tahan terhadap penambahan field baru, karena field baru tidak menggeser posisi argumen lama.
Prinsip Kedua: Sulit Disalahgunakan #
Kemudahan dipakai hanya separuh prinsip. Separuh lainnya memastikan jalur pemakaian yang salah tertutup. Bentuk kebocoran yang sering muncul adalah stringly typed API: pilihan yang seharusnya terbatas direpresentasikan sebagai &str bebas. Salah ketik satu huruf tidak terdeteksi kompilator, dan program diam-diam jatuh ke cabang default. Berikut program yang membandingkan API string dengan API enum:
// API bebas: string sebagai mode, salah ketik lolos dari kompilator
fn shutdown_v1(mode: &str) -> String {
match mode {
"force" => String::from("Mematikan paksa semua proses"),
"graceful" => String::from("Menyelesaikan proses berjalan lalu mematikan"),
_ => String::from("Mode tidak dikenal, memakai default: graceful"),
}
}
// Pilihan mode diangkat menjadi tipe: salah ketik tidak bisa dikompilasi
enum ShutdownMode {
Force,
Graceful,
}
fn shutdown_v2(mode: ShutdownMode) -> String {
match mode {
ShutdownMode::Force => String::from("Mematikan paksa semua proses"),
ShutdownMode::Graceful => String::from("Menyelesaikan proses berjalan lalu mematikan"),
}
}
fn main() {
// Salah ketik "gracefull" tetap berjalan, tapi diam-diam jatuh ke cabang default
let hasil_a = shutdown_v1("gracefull");
println!("Versi string: {}", hasil_a);
let hasil_b = shutdown_v2(ShutdownMode::Graceful);
println!("Versi enum: {}", hasil_b);
let hasil_c = shutdown_v2(ShutdownMode::Force);
println!("Versi enum: {}", hasil_c);
}Output yang Dihasilkan:
Versi string: Mode tidak dikenal, memakai default: graceful
Versi enum: Menyelesaikan proses berjalan lalu mematikan
Versi enum: Mematikan paksa semua prosesUraian komponen program di atas:
- Baris 2-8 (
fn shutdown_v1(mode: &str) { ... }): versi string harus menangkap semua kemungkinan lewat cabang default_. Cabang default menjadi tempat bersembunyi bagi setiap salah ketik, termasuk"gracefull"yang dipanggil pada baris 25. - Baris 11-14 (
enum ShutdownMode { Force, Graceful }): versi enum membatasi nilai sah menjadi tepat dua varian. Nilai ketiga tidak bisa masuk tanpa melewati kompilator. - Baris 16-21 (
fn shutdown_v2(mode: ShutdownMode) { ... }):matchpada enum tidak membutuhkan cabang default karena kompilator tahu seluruh varian sudah tertangani. Bila kelak kita menambah varianScheduled, kompilator justru memaksa kita memperbaruimatchini. - Baris 28 dan 31 (
shutdown_v2(ShutdownMode::Graceful)danshutdown_v2(ShutdownMode::Force)): pemanggil mengetik nama varian secara penuh. Salah ketik pada nama varian gagal kompilasi, bukan diam-diam jatuh ke cabang default.
Kesalahan ketik pada varian enum memang tertangkap kompilator. Program berikut menuliskan Gracefull dengan dua huruf l, lalu dikompilasi dengan rustc --edition 2021 main.rs:
enum ShutdownMode {
Force,
Graceful,
}
fn main() {
let mode = ShutdownMode::Gracefull;
let _ = mode;
}Pesan error kompilator:
error[E0599]: no variant, associated function, or constant named `Gracefull` found for enum `ShutdownMode` in the current scope
--> main.rs:7:30
|
1 | enum ShutdownMode {
| ----------------- variant, associated function, or constant named `Gracefull` not found for this enum
...
7 | let mode = ShutdownMode::Gracefull;
| ^^^^^^^^^ variant, associated function, or constant not found in `ShutdownMode`
|
help: there is a variant with a similar name
|
7 - let mode = ShutdownMode::Gracefull;
7 + let mode = ShutdownMode::Graceful;
|
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0599`.Error E0599 menegaskan bahwa nama Gracefull tidak ada pada enum ShutdownMode, lengkap dengan saran perbaikan menuju varian yang benar. Inilah bentuk nyata dari “sulit disalahgunakan”: kesalahan yang pada versi string hanya muncul sebagai perilaku aneh saat runtime, kini menjadi kegagalan kompilasi dengan petunjuk perbaikan.
Compiler sebagai Penjaga Kontrak #
Dua contoh di atas memperkenalkan pola pikir yang akan kita pakai di seluruh section ini: geser pemeriksaan dari runtime ke waktu kompilasi sedekat mungkin. Setiap chapter berikutnya menutup satu jenis celah dengan mekanisme Rust yang spesifik.
| Celah pemakaian yang salah | Mekanisme Rust yang menutup celah | Chapter |
|---|---|---|
| Dua nilai sejenis tertukar posisi | Struct dengan field bernama | Prinsip Desain API |
| Nilai tidak valid lolos ke konfigurasi | Validasi terkumpul di build() yang mengembalikan Result | Builder Pattern |
| Langkah dipanggil tidak berurutan | Tipe berbeda per state, method hanya tersedia pada state yang benar | Typestate Pattern |
| Operasi sekali-jalan dipanggil ulang | Method mengonsumsi self, ownership move mematikan nilai | Mengonsumsi self |
| Data yang melanggar aturan bisnis beredar bebas | Newtype dengan konstruktor tervalidasi | Newtype untuk Validasi Domain |
| Pesan error membocorkan detail internal | Error surface yang aman lewat Display | Desain Error pada API |
| Penamaan membingungkan pemanggil | Konvensi penamaan komunitas | Rust API Guidelines |
Pola pada tabel tersebut selalu sama: temukan invariants yang selama ini hanya hidup di dokumentasi atau di pemeriksaan runtime, lalu angkat invariants tersebut menjadi bagian dari tipe. Setelah aturan menjadi tipe, kompilator yang menjaganya — setiap pemanggilan yang melanggar ditolak dengan pesan error, bukan dibiarkan lewat untuk menjadi bug di produksi.
Ringkasan #
Desain API di Rust berdiri di atas dua prinsip: mudah dipakai berarti tanda tangan menjelaskan dirinya sendiri melalui nama yang baik dan tipe yang tepat, dan sulit disalahgunakan berarti jalur pemakaian yang salah ditutup sebelum program berjalan. Parameter boolean berurutan kita ganti dengan struct bernama; pilihan string bebas kita ganti dengan enum, dan salah ketik varian ditolak kompilator dengan E0599 lengkap beserta saran perbaikan. Enam chapter berikutnya menerapkan pola pikir tersebut pada satu jenis celah demi satu jenis celah, dimulai dari konfigurasi bertahap yang tervalidasi penuh lewat Builder Pattern.