Topik ini membahas Option<T> sebagai tipe yang menyatakan ketidakhadiran nilai, dengan penekanan pada cara mengambil isi nilai tanpa menggugurkan program. Pembahasan mencakup bahaya unwrap dan expect yang memicu panic, metode map, and_then, or_else, dan unwrap_or yang mempertahankan struktur Option selama pengolahan berlangsung, serta batas pemakaian unwrap pada contoh dan prototipe. Setelah membaca topik ini, rangkaian pencarian dan pengolahan nilai yang mungkin tidak hadir dapat ditulis sebagai satu jalur perhitungan yang aman, dan setiap titik yang berpotensi panic dapat dikenali serta dipisahkan dari jalur data utama.
Dua keadaan dalam satu tipe#
Setiap nilai Option<T> berada tepat di salah satu dari dua keadaan, yaitu Some(T) yang membawa satu nilai bertipe T, atau None yang tidak membawa data apa pun. Kompilator memperlakukan kedua keadaan itu sebagai satu tipe yang sama sehingga nilai T di dalamnya tidak dapat dipakai secara langsung. Pemakaian nilai harus melewati pencocokan pola seperti match dan if let, atau melalui metode milik Option. Sifat ini memindahkan kesalahan lupa memeriksa nilai kosong dari kejadian saat program berjalan menjadi error kompilasi, karena tidak ada jalur untuk membaca isi tanpa mengakui kemungkinan None.
Konsekuensi praktisnya, setiap pemanggil fungsi yang mengembalikan Option<T> dihadapkan pada percabangan dua jalur yang tergambar pada diagram berikut. Jalur Some melanjutkan perhitungan dengan nilai yang tersedia, sedangkan jalur None harus ditangani agar program tetap berjalan, atau dipaksa lewat unwrap yang berakhir panic. Pemilihan di antara kedua cara penanganan itu menjadi pokok bahasan bagian-bagian selanjutnya.
Program berikut memakai fungsi cari_nilai yang mengembalikan Option<u8> karena pencarian atas daftar nilai ujian memiliki dua hasil yang mungkin. Pemanggil menangani kedua hasil itu dengan match, lalu memakai if let pada kasus ketika kegagalan pencarian tidak memerlukan perlakuan khusus.
fn cari_nilai(daftar: &[(&str, u8)], nama: &str) -> Option<u8> {
for (siswa, nilai) in daftar.iter() {
if *siswa == nama {
return Some(*nilai);
}
}
None
}
fn main() {
let kelas = [("Ali", 88), ("Budi", 74), ("Citra", 92)];
// match menangani kedua kemungkinan secara eksplisit
match cari_nilai(&kelas, "Ali") {
Some(nilai) => println!("nilai Ali adalah {}", nilai),
None => println!("nilai Ali tidak tercatat"),
}
// if let cukup bila kemungkinan None tidak perlu ditangani khusus
if let Some(nilai) = cari_nilai(&kelas, "Citra") {
println!("nilai tertinggi sementara: {}", nilai);
}
}nilai Ali adalah 88
nilai tertinggi sementara: 92Fungsi cari_nilai mengembalikan Some(*nilai) begitu nama ditemukan dan None setelah perulangan selesai tanpa kecocokan. Nilai kembalian seperti ini tidak dapat dicampur dengan angka penanda seperti 0 yang maknanya harus dihafal. Keberhasilan dan kegagalan dibedakan oleh variant yang dibawa nilai kembalian, bukan oleh isi angkanya. Pola match pada main membuka isi Some ke nama nilai yang hanya hidup di lengan tersebut, sedangkan if let pada pemanggilan kedua cukup menangani satu variant dan mengabaikan None sama sekali. Kedua bentuk itu sama-sama sah, dan pilihan di antaranya ditentukan oleh perlakuan yang diperlukan ketika nilai tidak ditemukan.
unwrap dan expect sebagai jalan pintas#
Metode unwrap mengambil isi Some secara langsung dan menggugurkan program dengan panic apabila nilainya None. Metode expect berperilaku sama, tetapi menerima satu argumen teks yang menjadi pesan panic sehingga penyebabnya lebih mudah dikenali saat program berhenti. Keduanya tersedia karena pada banyak kasus, ketidakhadiran nilai memang mustahil menurut invarian program, yaitu kondisi yang dijamin selalu benar oleh logika program. Penulisan pencocokan pola penuh untuk kasus itu hanya menambah derau. Namun tiap pemakaian unwrap menanam satu titik panic yang tidak tercatat pada tanda tangan fungsi, sehingga pilihan ini perlu disengaja.
Program berikut memanggil unwrap pada nilai None untuk memperlihatkan bentuk kegagalannya.
export PATH="$HOME/.cargo/bin:$PATH"
# buat program yang memanggil unwrap pada nilai None
printf 'fn main() {\n let nama: Option<&str> = None;\n println!("sebelum unwrap");\n println!("{}", nama.unwrap());\n}\n' > laci-kosong.rs
# kompilasi lalu jalankan, nomor thread pada pesan bervariasi sehingga disaring
rustc --edition=2021 laci-kosong.rs -o laci-kosong && ./laci-kosong 2>&1 | sed -E 's/ \([0-9]+\)//' | sed -n '1,5p'sebelum unwrap
thread 'main' panicked at laci-kosong.rs:4:25:
called `Option::unwrap()` on a `None` value
note: run with `RUST_BACKTRACE=1` environment variable to display a backtracePesan panic memuat tiga bagian. Lokasi laci-kosong.rs:4:25 menunjukkan berkas, baris, dan kolom pemanggilan unwrap, pesan called Option::unwrap() on a None value menyebut kejadiannya, dan catatan terakhir mengingatkan variabel lingkungan RUST_BACKTRACE untuk menampilkan jejak tumpukan. Program berhenti segera setelah pesan itu tercetak sehingga pernyataan setelah unwrap tidak pernah berjalan. Nomor thread yang sebenarnya tercetak setelah kata main berbeda pada setiap jalankan sehingga disaring dari contoh, sedangkan bagian pesan lainnya tetap.
Dengan expect, pesan panic ditentukan sendiri sehingga pembaca keluaran tidak perlu menebak makna None pada baris tersebut.
export PATH="$HOME/.cargo/bin:$PATH"
# buat program yang memakai expect agar pesan panic jelas
printf 'fn main() {\n let nama: Option<&str> = None;\n println!("{}", nama.expect("nama wajib terisi"));\n}\n' > nama-wajib.rs
# jalankan, panic menampilkan pesan yang diberikan pada expect
rustc --edition=2021 nama-wajib.rs -o nama-wajib && ./nama-wajib 2>&1 | sed -E 's/ \([0-9]+\)//' | sed -n '1,4p'thread 'main' panicked at nama-wajib.rs:3:25:
nama wajib terisi
note: run with `RUST_BACKTRACE=1` environment variable to display a backtraceLokasi yang dilaporkan tetap menunjuk titik pemanggilan expect, sedangkan pesannya diganti menjadi argumen yang diberikan. Pesan khusus semacam ini sebaiknya menjelaskan invarian yang dilanggar, misalnya nama wajib terisi, bukan sekadar mengulang nama variabel yang kosong. Mekanisme lengkap penghentian program oleh panic dibahas pada topik panic! vs Result.
Combinator untuk mempertahankan struktur#
Combinator adalah metode yang mengolah nilai Option tanpa memecah strukturnya lebih dulu. Metode map menjalankan fungsi pada isi Some dan meneruskan None tanpa diubah, and_then melanjutkan ke langkah yang juga menghasilkan Option, or_else menyediakan sumber nilai cadangan, dan unwrap_or mengembalikan nilai cadangan secara langsung. Rangkaian combinator menggantikan match bersarang karena setiap langkah menyerahkan Option kepada langkah berikutnya dalam bentuk yang masih utuh.
Program berikut memakai keempat combinator tersebut atas hasil cari_nilai. Combinator map, and_then, dan or_else menerima closure, yaitu fungsi tanpa nama yang ditulis langsung sebagai argumen dengan bentuk |n| ....
fn cari_nilai(daftar: &[(&str, u8)], nama: &str) -> Option<u8> {
for (siswa, nilai) in daftar.iter() {
if *siswa == nama {
return Some(*nilai);
}
}
None
}
fn main() {
let kelas = [("Ali", 88), ("Budi", 74), ("Citra", 92)];
let susulan = [("Dewi", 80)];
// map: ubah isi Some tanpa memecah struktur Option
let puluhan = cari_nilai(&kelas, "Ali").map(|n| n / 10 * 10);
println!("puluhan Ali: {:?}", puluhan);
// and_then: lanjutkan ke langkah yang juga bisa gagal
let lulus_citra = cari_nilai(&kelas, "Citra")
.and_then(|n| if n >= 75 { Some(n) } else { None });
println!("nilai Citra bila lulus: {:?}", lulus_citra);
// unwrap_or: nilai cadangan tanpa panic
let nilai_dewi = cari_nilai(&kelas, "Dewi").unwrap_or(0);
println!("nilai Dewi tanpa catatan: {}", nilai_dewi);
// or_else: sumber kedua dihitung hanya jika sumber pertama None
let dari_susulan = cari_nilai(&kelas, "Dewi")
.or_else(|| cari_nilai(&susulan, "Dewi"));
println!("nilai Dewi dengan susulan: {:?}", dari_susulan);
}puluhan Ali: Some(80)
nilai Citra bila lulus: Some(92)
nilai Dewi tanpa catatan: 0
nilai Dewi dengan susulan: Some(80)Pada map, perhitungan n / 10 * 10 hanya berjalan ketika hasil pencarian bernilai Some, sehingga tidak ada pemeriksaan manual yang ditulis tangan. Closure pada and_then wajib mengembalikan Option, dan contoh di atas memakainya sebagai penyaring kelulusan yang menghasilkan None untuk nilai di bawah 75. Sumber cadangan pada or_else dievaluasi hanya ketika hasil pertama None, seperti terlihat pada nilai Dewi yang baru ditemukan setelah pencarian bergeser ke daftar susulan. Argumen unwrap_or sebaliknya selalu dievaluasi lebih dulu apa pun keadaan nilainya. Untuk menyaring nilai tanpa mengubah isinya, metode filter menyediakan bentuk yang lebih langsung daripada and_then.
Kesalahan umum#
Kesalahan pertama adalah menuliskan unwrap pada hasil pencarian yang wajar kosong, misalnya pencarian nama pada daftar yang isinya bergantung pada masukan pengguna. Nilai None pada kasus itu adalah kejadian normal, bukan pelanggaran invarian, sehingga unwrap mengubah kasus normal menjadi kegagalan total program. Perbaikannya adalah match, if let, atau unwrap_or dengan nilai cadangan yang masuk akal. Simpan unwrap hanya untuk nilai yang kehadirannya dijamin oleh logika program itu sendiri, misalnya hasil iter().next() pada iterator yang barusan dibuat dari koleksi tak kosong.
Kesalahan kedua adalah meletakkan perhitungan berat sebagai argumen unwrap_or dengan asumsi perhitungan itu hanya berjalan saat nilai kosong. Argumen unwrap_or dievaluasi lebih dulu pada setiap pemanggilan, termasuk ketika nilainya Some, sehingga biayanya tetap terbayar. Metode or_else menerima closure yang baru dievaluasi ketika hasil asalnya benar-benar None, dan bentuk unwrap_or_else menyediakan kebiasaan serupa untuk nilai cadangan biasa. Pilih bentuk yang sesuai biaya pembuatannya agar jalur sukses tidak membayar pekerjaan yang tidak pernah terpakai.
Ringkasan#
Option<T>menyatakan ketidakhadiran nilai melalui dua variant, dan isiSomehanya dapat diakses lewat pencocokan pola atau metode milikOption.unwrapdanexpectmengambil isiSomesecara langsung tetapi memicu panic padaNone, denganexpectmenyediakan pesan yang ditentukan sendiri.- Combinator
map,and_then,or_else, danunwrap_ormengolahOptiontanpa memecah strukturnya, sehinggamatchbersarang dapat dihindari. - Argumen
unwrap_orselalu dievaluasi lebih dulu, sedangkan sumber padaor_elsedievaluasi hanya ketika nilai asalnyaNone.
Referensi#
- The Option Enum and Its Advantages — The Rust Programming Language
— Perkenalan Option
sebagai pengganti null di Rust. - Error Handling — The Rust Programming Language — Kerangka Rust dalam memilah kegagalan tak terpulihkan dan yang dapat dipulihkan.
- Option — Rust standard library — Daftar metode Option beserta dokumentasi setiap combinator.