Nilai bertipe union sering kali harus digunakan sebagai salah satu tipenya saja, misalnya elemen dari array campuran String dan Int32. Topik ini membahas empat alat Crystal untuk keperluan itu, yaitu cast turun dengan as, bentuk aman as?, metode try pada nilai nilable, dan assertion not_nil!, beserta risiko yang menyertai alat yang mengabaikan pemeriksaan compiler.
Cast turun dengan as#
Pernyataan as menyatakan klaim bahwa sebuah nilai bertipe tertentu. Klaim yang sudah mustahil ditolak compiler sejak waktu kompilasi dengan pesan can't cast, sedangkan klaim yang masih mungkin lolos kompilasi dan diverifikasi ulang saat runtime. Cast yang ternyata salah melontarkan TypeCastError, sehingga pemanggilnya perlu struktur begin dan rescue apabila kecocokan tipe memang belum pasti.
# Cast turun dengan as: klaim tipe diverifikasi ulang saat runtime.
# Jalankan dengan: crystal konversi-as.cr
kumpulan = [24, "delapan", 5] of String | Int32
kumpulan.each do |nilai|
begin
angka = nilai.as(Int32)
puts "angka: #{angka}"
rescue e : TypeCastError
# pesan exception memuat lokasi berkas, dipotong agar ringkas
puts "ditolak: #{e.message.to_s.split(", at").first}"
end
end# menjalankan program cast turun
crystal konversi-as.crangka: 24
ditolak: Cast from String to Int32 failed
angka: 5Elemen kumpulan bertipe String | Int32 sehingga cast ke Int32 lolos kompilasi. Elemen pertama dan ketiga memang Int32 sehingga castnya berhasil, sedangkan elemen kedua berupa string sehingga runtime melontarkan TypeCastError dengan pesan Cast from String to Int32 failed. Klausa rescue menangkap exception itu sehingga perulangan dapat melanjutkan ke elemen berikutnya. Apabila cast ditujukan pada tipe yang sudah pasti tidak cocok, misalnya setelah pengecilan menunjukkan nilainya String, compiler menolaknya dengan pesan can't cast String to Int32 sebelum program sempat dijalankan.
Verifikasi ulang saat runtime diperlukan karena nilai sesungguhnya dari elemen sebuah array union baru diketahui ketika program berjalan. Compiler hanya mengetahui daftar tipe yang mungkin, sedangkan keputusan akhir menunggu nilai konkretnya. Pola dua lapis inilah yang membuat as tetap aman digunakan, yaitu klaim yang mustahil tertolak lebih awal dan klaim yang mungkin diuji saat pemakaiannya.
as? dan try#
Bentuk as? tidak melontarkan exception. Cast yang cocok mengembalikan nilainya, sedangkan cast yang gagal mengembalikan nil, sehingga hasilnya bertipe nilable dan dapat diperiksa seperti hasil pencarian. Metode try bekerja pada nilai nilable dengan menjalankan bloknya hanya ketika nilainya bukan nil, dan mengembalikan nil tanpa menjalankan blok apabila nilainya nil.
# as? dan try: pemeriksaan tipe yang tidak melempar exception.
# Jalankan dengan: crystal aman.cr
nilai : String | Int32 = "tiga"
puts "as? String: #{nilai.as?(String).inspect}"
puts "as? Int32: #{nilai.as?(Int32).inspect}"
nama : String? = nil
puts "try pada nil: #{nama.try(&.size).inspect}"
nama = "Ali"
puts "try pada String: #{nama.try(&.size).inspect}"
bilangan : Int32? = 21
puts "try dengan blok: #{bilangan.try { |v| v * 2 }.inspect}"# menjalankan program pemeriksaan aman
crystal aman.cras? String: "tiga"
as? Int32: nil
try pada nil: nil
try pada String: 3
try dengan blok: 42Pada program di atas, as?(String) mengembalikan isi aslinya sedangkan as?(Int32) mengembalikan nil, dan keduanya diperiksa dengan inspect agar nil terlihat jelas. Metode try pada variabel nama tidak menjalankan size ketika namanya nil dan mengembalikan nil. Bentuk try &.size merupakan singkatan untuk satu panggilan metode, sedangkan bentuk dengan blok biasa cocok untuk ekspresi yang lebih panjang seperti v * 2.
Hasil as? maupun try sama-sama bertipe nilable sehingga keduanya dapat dirangkai dengan pemeriksaan berikutnya, misalnya percabangan if terhadap nil. Rangkaian seperti ini memindahkan penanganan ketidakcocokan tipe dari blok rescue ke alur data yang biasa, dan program tidak perlu menangkap exception untuk keadaan yang memang wajar.
Assertion dengan not_nil!#
Metode not_nil! mengubah nilai nilable menjadi nilai biasa dengan satu syarat, yaitu nilainya memang bukan nil. Apabila syarat itu dilanggar, metode ini melontarkan NilAssertionError dengan pesan Nil assertion failed. Nilai baliknya bertipe asli tanpa nil, sehingga pemanggilan metode yang menuntut tipe lengkap dapat dilakukan langsung setelahnya. Assertion, yaitu pernyataan paksa atas keadaan program, tepat digunakan ketika logika di sekitarnya sudah menjamin keberadaan nilai, misalnya setelah langkah pemeriksaan yang sudah dilakukan sebelumnya.
# not_nil!: assertion yang mematikan pengecilan compiler.
# Jalankan dengan: crystal not-nil.cr
nama : String? = "Citra"
puts "panjang nama: #{nama.not_nil!.size}"
nama = nil
begin
nama.not_nil!
rescue e : NilAssertionError
puts "ditolak: #{e.message}"
end# menjalankan program assertion not_nil!
crystal not-nil.crpanjang nama: 5
ditolak: Nil assertion failedPemanggilan pertama berhasil karena namanya "Citra", dan hasilnya langsung digunakan untuk menghitung panjang. Pemanggilan kedua dilakukan pada variabel yang berisi nil, sehingga NilAssertionError terlempar dan ditangkap rescue beserta pesannya.
Risiko mengabaikan pengecilan compiler#
Percabangan is_a? mengecilkan tipe variabel di dalam cabangnya, dan kebenaran tipe itu dijamin compiler karena pemeriksaannya bersifat statis.
nilai : String | Int32 = "halo"
if nilai.is_a?(String)
puts "teks: #{nilai.upcase}"
endteks: HALOPada contoh di atas, compiler tahu bahwa nilai bertipe String di dalam cabang if, sehingga pemanggilan upcase dijamin valid tanpa pemeriksaan apa pun saat runtime. Perilaku ini berbeda dari as dan not_nil!. Keduanya menghentikan pengecilan compiler karena tipe sudah diklaim secara paksa, dan klaim itu tidak diverifikasi ulang oleh compiler. Apabila klaimnya keliru, kegagalan baru terasa saat runtime dalam bentuk TypeCastError atau NilAssertionError.
Kaidah praktisnya, perlakukan as dan not_nil! sebagai klaim yang menuntut bukti. Bukti itu dapat berupa pemeriksaan yang sudah dilakukan, data yang bentuknya dijamin oleh kode lain, atau kesepakatan dengan pustaka eksternal. Tanpa bukti, pilih is_a?, as?, atau try karena ketiganya membiarkan compiler tetap memeriksa program. Kegagalan runtime dari klaim yang keliru masih dapat ditangkap rescue seperti exception lain, tetapi menangkapnya setelah keadaan program kacau jauh lebih sulit daripada mencegahnya sejak waktu kompilasi.
Ringkasan#
- Pernyataan
asmenurunkan tipe dengan klaim yang diverifikasi saat runtime dan melontarkanTypeCastErrorketika klaimnya salah. - Bentuk
as?mengembalikannilalih-alih exception, sedangkantrymenjalankan bloknya hanya pada nilai yang bukannil. - Metode
not_nil!mengembalikan nilainya atau melontarkanNilAssertionErrordengan pesanNil assertion failed. asdannot_nil!mematikan pengecilan compiler, sehinggais_a?dan percabangan biasa lebih aman untuk tipe yang belum pasti.
Referensi#
- Dokumentasi bahasa Crystal 1.22 — Crystal
— menjelaskan cast dengan
asdanas?, metodetry, serta pengecilan tipe pada percabanganis_a?. - Referensi API Crystal 1.22.1 — Crystal
— merinci
TypeCastError,NilAssertionError, dan metodenot_nil!pada tipe nilable.