Sejauh ini setiap ekspresi yang kita tulis menghasilkan nilai dengan tipe tertentu: 5 bertipe i32, "Halo" bertipe &str, dan pemanggilan Vec::new() bertipe Vec<T>. Namun ada kelompok kode yang sifatnya berbeda: panic! menghentikan program, process::exit() mematikan proses, dan loop {} tanpa break berjalan selamanya — tidak satu pun dari mereka pernah menghasilkan nilai untuk dipakai baris berikutnya, karena baris berikutnya tidak pernah terjadi. Rust memodelkan situasi ini dengan never type, ditulis !. Pemahaman tipe ini menjelaskan keanehan yang mungkin sudah kita temui: mengapa panic! boleh duduk berdampingan dengan &str di dalam satu match, padahal aturan tipe Rust terkenal ketat. Jawabannya ada pada kemampuan ! menyatu dengan tipe apa pun.
Ekspresi yang Tidak Pernah Menghasilkan Nilai #
Never type menjawab pertanyaan sederhana: apa tipe dari ekspresi yang tidak pernah selesai dievaluasi? Ketika panic! dieksekusi, stack mulai dibongkar (unwinding) dan program berhenti; ketika process::exit(101) berjalan, proses langsung dimatikan sistem operasi. Ekspresi seperti ini dikatakan diverging — mengalir keluar dari jalur normal program dan tidak pernah kembali membawa nilai. Rust memberi seluruh ekspresi diverging tersebut tipe !.
Dua bukti penting mengikuti definisi ini. Pertama, fungsi yang selalu mengakhiri program boleh mendeklarasikan tipe kembalian !, dan deklarasi tersebut sekaligus menjanjikan bahwa fungsi tidak punya jalur yang kembali normal. Kedua — dan inilah yang paling sering kita manfaatkan — ! dapat menyatu (unify) dengan tipe apa pun. Karena nilai bertipe ! tidak pernah benar-benar ada saat program berjalan, kompilator membiarkannya berpura-pura menjadi tipe apa pun yang dibutuhkan konteksnya.
! Menyatukan Tipe pada match #
Aturan match menuntut semua arm menghasilkan tipe yang sama. Di sinilah ! bekerja: arm yang diverging tidak perlu mencocokkan tipe arm lainnya karena arm tersebut tidak pernah menghasilkan nilai. Pola yang paling umum adalah arm terakhir yang memanggil panic!:
fn main() {
let grade = "B";
let description: &str = match grade {
"A" => "sangat baik",
"B" => "baik",
"C" => "cukup",
_ => panic!("nilai tidak dikenal: {}", grade),
};
println!("Deskripsi nilai: {}", description);
}Output yang Dihasilkan:
Deskripsi nilai: baikMari kita bedah kode di atas:
- Baris 4 (
let description: &str = match grade {): hasilmatchdiikat ke variabel bertipe&str. Seluruh arm harus menyepakati tipe tersebut. - Baris 5-7 (
"A" => "sangat baik",dst.): tiga arm pertama menghasilkan&strkarena setiap literal teks bertipe&str. - Baris 8 (
_ => panic!("nilai tidak dikenal: {}", grade),): arm penangkap sisa ini menghasilkan tipe!, bukan&str. Kompilator menerima perbedaan ini karenapanic!diverging — tidak ada nilai yang perlu dikonversi. Ketika grade di luar A, B, dan C muncul, program berhenti di baris ini dan variabeldescriptiontidak pernah dibuat. - Baris 11 (
println!("Deskripsi nilai: {}", description);): baris ini hanya tercapai bila arm 5-7 yang menang, sehinggadescriptiondijamin bertipe&strdan aman dicetak.
Pola yang sama bekerja pada Option: arm None => panic!("konfigurasi belum diatur") pada hasil pembacaan konfigurasi, atau arm Err(message) => panic!(...) pada hasil parsing — semuanya berdiri di atas unifikasi !.
Ketika Arm Tidak Divergen #
Mekanisme ini mudah disalahpami sebagai “arm terakhir bebas bertipe apa pun”. Padahal izin khusus hanya berlaku untuk ekspresi diverging. Begitu arm terakhir diganti ekspresi biasa yang tipe berbeda, kompilator langsung menolak. Perhatikan percobaan mengganti panic! dengan format!:
fn main() {
let grade = "B";
let description: &str = match grade {
"A" => "sangat baik",
"B" => "baik",
_ => format!("nilai tidak dikenal: {}", grade),
};
println!("Deskripsi nilai: {}", description);
}Program di atas gagal dikompilasi:
error[E0308]: mismatched types
--> main.rs:7:14
|
7 | _ => format!("nilai tidak dikenal: {}", grade),
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `&str`, found `String`
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0308`.Pesan E0308 menegaskan batasnya: format! adalah ekspresi biasa yang menghasilkan String dan bisa saja benar-benar tereksekusi, sehingga arm tersebut harus cocok dengan tipe arm lain (&str). Kesimpulannya tajam: ! menyatu dengan semua tipe justru karena ! dijamin tidak pernah menghasilkan nilai; String tidak punya jaminan itu, jadi String harus mengikuti aturan umum.
Fungsi Bertipe Kembalian ! #
Menulis fungsi sendiri yang diverging memakai anotasi -> !. Tipe kembalian ini menjanjikan kepada kompilator bahwa semua jalur fungsi berujung pada penghentian program. Janji tersebut diperiksa: fungsi bertipe ! yang masih punya jalur kembali normal akan ditolak kompilator. Contoh penerapannya pada penanganan kondisi fatal:
fn abort_with_error(message: &str) -> ! {
println!("ERROR FATAL: {}", message);
std::process::exit(101);
}
fn main() {
let disk_space_gb: Option<u32> = None;
let space = match disk_space_gb {
Some(value) => value,
None => abort_with_error("ruang disk tidak terdeteksi"),
};
println!("Ruang disk tersedia: {} GB", space);
}Karena disk_space_gb bernilai None, program berhenti di tengah jalan. Kompilasi dan eksekusinya:
rustc --edition 2021 main.rs -o never-demo && ./never-demo; echo "exit code: $?"Output yang Dihasilkan:
ERROR FATAL: ruang disk tidak terdeteksi
exit code: 101Mari kita bedah kode dan hasilnya:
- Baris 1 (
fn abort_with_error(message: &str) -> ! {): tipe kembalian!menyatakan fungsi ini tidak pernah kembali membawa nilai. Signature semacam ini membuat fungsi dapat ditempatkan pada posisi yang menuntut tipe apa pun. - Baris 3 (
std::process::exit(101);): pemanggilan yang mematikan proses dengan exit code 101. Ekspresi ini bertipe!, dan karena baris inilah satu-satunya jalur fungsi, janji-> !terpenuhi. - Baris 11 (
None => abort_with_error(...)): armNonememanggil fungsi bertipe!, sehingga tipematchditentukan seluruhnya oleh armSome—u32— dan variabelspacedijaminu32di baris selanjutnya. - Output program: teks “ERROR FATAL: ruang disk tidak terdeteksi” tercetak, baris
println!dimaintidak pernah tercapai, dan proses berakhir dengan exit code 101 yang terbaca lewatecho "exit code: $?".
continue dan break dalam Posisi Ekspresi #
Unifikasi ! juga bekerja pada continue dan break, yang sama-sama mengalir keluar dari ekspresi sebelum nilai dihasilkan. Kedua kata kunci ini memungkinkan pola “saring di tengah ekspresi”:
fn main() {
let sensor_readings = [21, 0, 19, 0, 24];
let mut valid_total = 0;
for reading in sensor_readings {
let value: i32 = if reading == 0 {
continue;
} else {
reading
};
valid_total += value;
}
println!("Total pembacaan valid: {}", valid_total);
}Output yang Dihasilkan:
Total pembacaan valid: 64Mari kita amati baris kuncinya:
- Baris 6-10 (
let value: i32 = if reading == 0 { ... } else { reading };): cabangifberisicontinue;yang bertipe!, sedangkan cabangelseberisireadingyang bertipei32. Kedua cabang tidak perlu bertipe sama: cabang pertama diverging, jadi tipe seluruh ekspresiifmengikuti cabangelse, yaitui32. - Baris 7 (
continue;): saat pembacaan sensor bernilai 0,continuemelempar eksekusi ke iterasi berikutnya. Pernyataanlet valueuntuk iterasi tersebut tidak pernah menyelesaikan evaluasinya, sehingga tidak ada nilai!yang perlu menjadii32. - Hasil akhir: pembacaan 0 dilewati dua kali, dan
21 + 19 + 24menghasilkan 64 — sesuai dengan output yang tercetak.
Ringkasan #
Never type ! adalah tipe untuk ekspresi diverging — panic!, process::exit, loop {} tanpa break, continue, dan break — yang dijamin tidak pernah menghasilkan nilai. Jaminan inilah yang memberi ! kemampuan menyatu dengan tipe apa pun: arm match berisi panic! diterima di samping arm &str, cabang if berisi continue boleh dipasangkan dengan cabang i32, dan fungsi bertipe kembalian ! dapat duduk di posisi nilai bertipe apa pun yang dijanjikan. Ketika arm diverging diganti ekspresi biasa seperti format!, perlindungan hilang dan E0308 muncul kembali seperti biasa. Kita sekarang beralih dari tipe tanpa nilai ke tipe tanpa ukuran tetap: sized dan unsized pada Chapter berikutnya.