- Menjelaskan pendekatan desain Rust yang memanfaatkan sistem tipe untuk menjamin invariant pada waktu kompilasi alih-alih validasi saat runtime
- Mendefinisikan tipe baru (newtype) seperti
NonZeroF32, NonEmptyVec untuk membuat status yang salah (0, vektor kosong, dan sebagainya) tidak bisa direpresentasikan
- Alih-alih mengembalikan kegagalan dengan
Option atau Result, pendekatan ini memperketat batasan pada argumen fungsi agar error dicegah sejak awal
- Menunjukkan contoh seperti
String::from_utf8 atau serde_json::from_str yang mengubah data menjadi tipe bermakna melalui parsing
- Prinsip desain untuk membuat status ilegal tidak dapat direpresentasikan dan memajukan validasi sedini mungkin meningkatkan stabilitas serta keterbacaan kode
1. Mengekspresikan batasan dengan tipe, bukan validasi runtime
- Pada fungsi
divide(a, b), pembagian dengan 0 menyebabkan panic saat runtime
- Kegagalan bisa direpresentasikan dengan mengembalikan
Option, tetapi ini melemahkan tipe hasil
- Dengan mendefinisikan tipe
NonZeroF32, hanya nilai bukan 0 yang dapat dibuat
- Konstruktor berbentuk
fn new(n: f32) -> Option<NonZeroF32>, yang mengembalikan None saat gagal
- Jika didefinisikan sebagai
divide_floats(a: f32, b: NonZeroF32), validasi runtime tidak lagi diperlukan
- Tanggung jawab validasi dipindahkan dari dalam fungsi ke sisi pemanggil, sehingga error dihilangkan lebih awal
2. Menghilangkan validasi duplikat dan menyederhanakan kode
- Pada fungsi
roots(a, b, c), jika pemeriksaan a == 0 ditangani dengan Option, validasi duplikat muncul baik di pemanggil maupun di fungsi
- Dengan memakai
NonZeroF32, validasi cukup dilakukan sekali, lalu logika setelahnya menjadi lebih sederhana
- Prinsip yang sama bisa dipakai untuk mendefinisikan
NonEmptyVec<T> agar vektor kosong tidak diizinkan
- Jika
get_cfg_dirs() mengembalikan NonEmptyVec<PathBuf>, maka validasi tambahan di main() tidak diperlukan lagi
3. Contoh nyata: String dan serde_json
String secara internal adalah tipe baru (newtype) dari Vec<u8>, dan String::from_utf8 melakukan pemeriksaan validitas
- Setelah itu, nilainya bisa dipakai dengan aman sebagai string yang dijamin UTF-8
serde_json from_str::<Sample> mem-parse JSON menjadi struct dan menjamin keberadaan field serta konsistensi tipe pada waktu kompilasi
- Semua batasan seperti keberadaan field
foo, bar, kecocokan tipe, panjang array, dan lainnya diperiksa pada level tipe
4. Dua prinsip desain berbasis tipe
- Membuat status ilegal tidak dapat direpresentasikan
NonZeroF32 tidak bisa merepresentasikan 0, dan NonEmptyVec tidak bisa merepresentasikan keadaan kosong
- Fungsi validasi sederhana seperti
is_nonzero tetap memungkinkan status yang salah direpresentasikan, sehingga tidak lengkap
- Lakukan validasi sedini mungkin
- Jika validasi tersebar di seluruh kode seperti pada ‘Shotgun Parsing’, hal itu bisa berujung pada celah keamanan (CVE-2016-0752, dan lain-lain)
- Jika semua batasan diperiksa pada tahap parsing, logika setelahnya dapat berjalan dengan aman
5. Pembuktian berbasis tipe di Rust dan penerapannya
- Menurut korespondensi Curry-Howard, tipe dapat dipandang sebagai proposisi logika, dan nilai sebagai buktinya
- Dengan crate
typenum, relasi matematis (3 + 4 = 8) dapat diverifikasi pada waktu kompilasi
- Sistem tipe dapat digunakan untuk membuktikan kebenaran program pada saat kompilasi
6. Saran penerapan di dunia kerja
- Meski API eksternal meminta tipe sederhana (
bool, i32), di sisi internal sebaiknya tetap direpresentasikan sebagai enum atau newtype yang bermakna
- Contoh: definisikan
LightBulbState { On, Off } lalu implementasikan From<LightBulbState> for bool
- Jika ada fungsi validasi sederhana seperti
verify() atau do_something_fallible(), pertimbangkan transformasi ke tipe terstruktur melalui parsing
- Untuk fungsi tanpa efek samping, status yang sengaja dibuat mustahil bisa diekspresikan lewat tipe seperti
Result<Infallible, MyError>
7. Kesimpulan
- Dengan memanfaatkan sistem tipe Rust sebagai alat validasi, kejelasan dan stabilitas kode dapat ditingkatkan
- Berbagai alat di ekosistem Rust seperti
Vec, sqlx, bon sudah memanfaatkan desain berbasis tipe
- Tidak semua masalah bisa diselesaikan dengan tipe, tetapi pendekatan menaikkan logika validasi ke level tipe meningkatkan kemudahan pemeliharaan dan keamanan
- Disarankan memanfaatkan sistem tipe Rust yang kuat semaksimal mungkin agar kompiler yang menangkap error lebih awal
1 komentar
Komentar Hacker News
Contoh pembagian dengan nol yang digunakan dalam tulisan ini kurang tepat untuk menjelaskan prinsip “Parse, Don’t Validate”
Inti prinsip ini ada pada fungsi yang mengubah data tak tepercaya menjadi tipe yang secara struktural benar
Dalam tulisan Alexis King "Names are not type safety", disebutkan juga bahwa pola
newtypetidak dapat menjamin ‘correct by construction’ secara penuhKetika sistem tipe tidak bisa mengekspresikan invariant secara langsung, pendekatan yang realistis adalah memakai tipe abstrak dengan smart constructor yang meniru parser
Contoh kedua, yaitu non-empty vec, merupakan kasus yang jauh lebih baik karena menjamin di dalam sistem tipe bahwa “selalu ada setidaknya satu elemen”
newtypetetap sangat berguna dalam praktikSaat asal sebuah string tidak jelas, nilai yang dienkapsulasi sangat meningkatkan keandalan
Untuk correctness-by-construction yang sepenuhnya, dibutuhkan sistem tipe dependen, tetapi ada juga alternatif ringan seperti pattern types di Rust
Misalnya, bisa membatasi rentang seperti
i8 is 0..100atau mengekspresikan slice yang tidak kosong dengan[T] is [_, ..]Namun, non-empty list berbentuk
(T, Vec<T>)adalah contoh benturan antara kepraktisan dan kemurnian teoretis, karena ada banyak batasan jika ingin memperlakukannya seperti vektorTipe seperti
NonZeroU32memang sederhana, tetapi kekuatan sebenarnya ada pada merancang seluruh logika domain sebagai tipe sehingga compiler bertindak sebagai penjaga gerbangDengan begitu, beban debugging berpindah dari runtime ke tahap perancangan
Sebagai contoh, "Domain Modeling Made Functional" dan video terkait layak dilihat
Daripada mencoba membungkusnya pada level seperti itu, akan lebih jelas bila membungkus perilaku fungsi aritmetika seperti overflow
Saya merangkum tautan ke diskusi terkait terbaru
Parse, Don't Validate (2019) (Februari 2026, 172 komentar)
Parse, Don’t Validate – Some C Safety Tips (Juli 2025, 73 komentar)
Parse, Don't Validate (2019) (Juli 2024, 102 komentar) dan lain-lain
Ini dibagikan hanya sebagai referensi
Pendekatan parsing over validation punya keterbatasan ketika kita tidak bisa mengetahui semua kemungkinan kasus di dunia nyata
Untuk hal seperti format file, membuatnya gagal sedini mungkin itu bagus, tetapi penerapannya pada logika bisnis atau pemodelan transisi status perlu hati-hati
Jika kebutuhan di dunia nyata berubah, sistem bisa gagal mengakomodasinya dan pada akhirnya pengguna akan mencari jalan memutar
Dalam bahasa lain, pendekatan ini bisa dibawa lebih jauh dengan dependent typing
Misalnya,
get_elem_at_index(array, index)dapat menjamin rentang indeks pada waktu kompilasi meskipun panjang array tidak diketahui sebelumnyaTipe
Vect n adanFin ndi Idris adalah contohnyaContoh: anodized (video pengantar)
Ada juga pendekatan dengan menaruh banyak fungsi pada satu tipe
Ini seperti di Clojure, yang merepresentasikan semua data dengan satu map dan memungkinkan seluruh standard library memanipulasinya
Invariant penting bisa dimasukkan ke dalam tipe, atau diekspresikan sebagai fungsi sederhana
Bahkan dalam bahasa bertipe dinamis pun ada kebiasaan desain yang menghasilkan efek serupa
Input eksternal tetap harus di-parse pada akhirnya, jadi ini bukan pengganti total
Dalam sistem tipe struktural, kita bisa meniru tipe nominal dengan branding, dan sebaliknya juga bisa, tetapi kurang ergonomis
Pada akhirnya, yang realistis adalah mencampurkan kedua pendekatan dengan tepat
Diskusi ini mengingatkan pada fitur concepts di C++
Dalam Concept-based Generic Programming karya Bjarne Stroustrup, ada contoh validasi otomatis untuk konversi integer
Tipe seperti
Number<unsigned int>atauNumber<char>akan melempar exception jika nilainya berada di luar rentangContoh
try_rootsdalam tulisan itu sebenarnya adalah kontra-contohUntuk mengekspresikan batasan
b^2 - 4ac >= 0sebagai tipe, hal itu menjadi sangat rumit di RustDalam kasus seperti ini, lebih masuk akal untuk cukup mengembalikan
Optiondan melakukan validasi di dalam fungsiSebagian besar validasi menangani interaksi antara banyak nilai, sehingga kurang nyaman diselesaikan lewat “parsing”
fn(abc: ValidABC)Pola ini juga cocok untuk desain API
Daripada memvalidasi request JSON, lebih baik langsung meng-parse-nya menjadi struct yang dijamin oleh tipe sehingga logika setelahnya tidak perlu validasi berulang
Ini mudah diterapkan di Rust dengan kombinasi serde + custom deserializer
Saya pernah melihat kasus nyata di mana pendekatan seperti ini mengurangi kode penanganan error sebesar 60%
Filosofi yang sama juga diterapkan pada UI design system
Alih-alih memeriksa CSS belakangan, didefinisikan tipe yang hanya mengizinkan penempatan berdasarkan satuan grid, sehingga margin arbitrer seperti 13px menjadi error saat kompilasi
Dengan cara ini, desain tetap deterministik
records + pattern matching di C# mendekati pendekatan ini
discriminated unions di F# lebih kuat lagi, sehingga dengan
Result<'T,'Error>kita bisa membuat status tidak valid menjadi mustahil direpresentasikanJika C# nantinya mengadopsi DU native, hasilnya akan jauh lebih rapi