HeadlinesBriefing favicon HeadlinesBriefing.com

Parse Don't Validate di Rust: Vektor Non-Empty

Hacker News •
×

Seperti banyak pemrogram, saya menemukan artikel Alexis King tentang 'Parse, don't validate' sangat menarik, karena artikel ini memberikan nama pada idiom yang terasa familiar dan penting—idiom yang telah saya amati dan gunakan di masa lalu tanpa menamainya secara eksplisit. Artikel ini adalah ulasan tentang pola 'Parse, don't validate' yang diterapkan pada bahasa pemrograman Rust (artikel asli menggunakan Haskell). Saya sangat tertarik untuk menemukan contoh-contoh edukatif dari pola ini di pustaka standar Rust dan proyek-proyek terkenal lainnya. Tanpa mengulangi artikel asli (silakan baca artikelnya terlebih dahulu!), berikut adalah intinya. Pertimbangkan vektor klasik; metode pertamanya mengembalikan Option<&T>. Mengapa? Karena vektor tidak dijamin memiliki elemen, jadi apa yang terjadi jika first dipanggil pada vektor kosong? Mengembalikan Option dalam kasus ini adalah praktik yang umum di Rust [1], dengan sintaks gula yang nyaman untuk menerima hasil dari fungsi yang mengembalikan Option dan memutuskan apa yang harus dilakukan selanjutnya. Jadi, apa masalahnya? Bayangkan kita memiliki fungsi untuk membaca beberapa jalur konfigurasi dari variabel lingkungan, sambil memastikan bahwa daftar tidak boleh kosong: use anyhow::{Result, ensure}; fn get_configuration_directories() -> Result<Vec<Path Buf>> { let value = env::var("CONFIG_DIRS").context("could not read CONFIG_DIRS")?; let directories: Vec<Path Buf> = value.split(',').map(str::trim).map(Path Buf::from).collect(); ensure!(!directories.is_empty(), "empty CONFIG_DIRS"); Ok(directories) } Sampai sekarang, semuanya baik-baik saja. Sekarang mari kita lihat penggunaan khas dari fungsi ini: fn main() -> Result<()> { let config_dirs = get_configuration_directories()?; match config_dirs.first() { Some(cache_dir) => initialize_cache(cache_dir), None => unreachable!("already checked that CONFIG_DIRS is non-empty"); } Ok(()) } Setelah get_configuration_directories mengembalikan hasil yang sukses, kita dijamin bahwa vektor tidak kosong. Namun, jika kita ingin mendapatkan elemen pertama dari vektor ini, kita harus menggunakan metode first yang mengembalikan Option<&T>. Oleh karena itu, kita dipaksa lagi untuk menangani kasus yang mungkin kosong (di mana opsi adalah None). Seperti yang dinyatakan dalam artikel asli, ini memiliki sejumlah masalah dengan kejelasan kode, implikasi kinerja potensial, dan bom waktu jika invariant dalam get_configuration_directories pernah diubah. Masalah intinya adalah bahwa Vec pada dasarnya adalah tipe yang dapat kosong; kita dapat menulis komentar "Ini tidak boleh kosong, janji kecil!" pada semua kode yang relevan, tetapi ini tidak diverifikasi secara formal oleh apa pun. Tipe untuk vektor "non-empty" Solusinya adalah memanfaatkan sistem tipe untuk menegakkan invariant yang baru ditetapkan. Kita dapat menggunakan tipe terpisah untuk "vektor yang tidak boleh kosong"; faktanya, tipe seperti itu sudah ada di beberapa crates Rust—misalnya nonempty: pub struct Non Empty<T> { pub head: T, pub tail: Vec<T>, } Tipe ini tidak memiliki konstruktor yang mengizinkan "tidak ada elemen"; konstruktor new-nya mengambil satu elemen, dan metode first-nya mengembalikan &T tanpa Option: pub const fn new(e: T) -> Self { Self::singleton(e) } pub const fn singleton(head: T) -> Self { Non Empty { head, tail: Vec::new() } } pub const fn first(&self) -> &T { &self.head } Sisa dari crate ini bertujuan untuk membuat Non Empty berperilaku sedekat mungkin dengan Vec biasa, dengan mengimplementasikan banyak trait yang berguna, serta konversi seperti: pub fn from_vec(mut vec: Vec<T>) -> Option<Non Empty<T>> { if vec.is_empty() { None } else { let head = vec.remove(0); Some(Non Empty { head, tail: vec }) } } Mari kita lihat bagaimana fungsi get_configuration_directories kita akan terlihat jika mengembalikan Non Empty daripada Vec biasa: fn get_configuration_directories() -> Result<Non Empty<Path Buf>> { let value = env::var("CONFIG_DIRS").context("could not read CONFIG_DIRS")?; let directories = value.split(',').map(str::trim).map(Path Buf::from).collect(); let Some(directories) = Non Empty::from_vec(directories) else { bail!("CONFIG_DIRS cannot be empty"); }; Ok(directories) } Perhatikan penggunaan Non Empty::from_vec di sini—ini adalah tempat di mana invariant ditetapkan. Sekarang...

FAQ Pertanyaan: Apa manfaat utama menggunakan tipe vektor non-empty di Rust?

FAQ Jawaban: Manfaat utamanya adalah bahwa hal ini menegakkan invariant non-empty pada tingkat tipe, menghilangkan kebutuhan akan pemeriksaan runtime dan penanganan Option ketika mengakses elemen pertama, sehingga meningkatkan kejelasan dan keamanan kode.