Optional adalah tipe yang menyatakan sebuah nilai atau ketiadaannya secara eksplisit. Topik ini membahas tipe ?T, pembukaan nilai dengan .?, nilai cadangan dengan orelse, penangkapan nilai dengan if dan while, serta pointer opsional ?*T dan perbandingannya dengan pointer sentinel. Sebagai contoh penerapan, pencarian nomor kartu anggota dalam array dibuat mengembalikan indeks atau null.
Konsep#
Tipe optional ?T#
Tipe optional ditulis dengan tanda tanya di depan tipe dasarnya, misalnya ?u32. Nilai bertipe ?u32 berupa bilangan u32 atau null, dan tidak ada kemungkinan ketiga. Karena ketiadaan nilainya dinyatakan secara eksplisit, pemanggil fungsi tidak perlu menebak apakah nilai 0 atau -1 berarti kosong, dan kompilator menuntut penanganan null sebelum nilai dapat dipakai sebagai u32.
// tidak-dijalankan
var stok: ?u32 = 24;
stok = null;
// stok kini menyatakan ketiadaan nilaiNilai null bukan bagian dari u32, sehingga penugasan stok = null hanya sah karena tipenya optional. Perbandingan tetap nyaman dipakai. Ekspresi stok == 24 bernilai benar hanya bila stok berisi 24, sedangkan stok != null memeriksa keberadaan nilainya. Ukuran ?u32 lebih besar daripada u32 karena perlu tempat menyimpan penanda apakah nilainya ada, sedangkan ?*T justru berukuran sama dengan *T, seperti dijelaskan pada bagian pointer opsional.
Membuka nilai dengan .?#
Operator .? mengambil nilai di dalam optional, dan kegiatan ini disebut membuka (unwrap) nilai. Hasilnya bertipe T murni dan siap dipakai. Apabila optional yang dibuka ternyata null, program berhenti dengan panic yang menyatakan penggunaan nilai null pada mode Debug. Dengan demikian, .? pada hakikatnya adalah pernyataan bahwa null di posisi itu tidak mungkin.
// tidak-dijalankan
const stok: ?u32 = 24;
const pasti: u32 = stok.?; // sah karena stok tidak null
// bila stok null, baris di atas menghentikan programPembukaan ini aman dipakai ketika keberadaan nilai sudah diperiksa lebih dahulu atau ketika kontrak fungsi menjamin nilainya. Di luar itu, .? menyerahkan risiko kepada jalannya program. Panic semacam itu tetap lebih baik daripada melanjutkan proses dengan nilai salah, tetapi bukan pola yang dianjurkan untuk kasus yang bisa ditangani dengan rapi. Bagian berikutnya menunjukkan dua alternatif yang menyatakan rencana cadangan secara eksplisit.
Nilai cadangan dengan orelse dan penangkapan if#
Operator orelse menyatakan nilai cadangan (fallback) yang dipakai bila optional bernilai null. Ekspresi stok orelse 0 menghasilkan u32 dalam kedua keadaan, sehingga tidak ada null yang tersisa setelahnya. Untuk alur yang lebih besar, orelse boleh diikuti blok, misalnya untuk mencetak pesan lalu return dari fungsi.
Penangkapan dengan if menawarkan cabang ganda. Pernyataan if (stok) |nilai| menjalankan blok pertama dengan nilai yang sudah terbuka, sedangkan cabang else menangani keadaan null.
const std = @import("std");
pub fn main() void {
var stok: ?u32 = 24;
if (stok) |nilai| {
std.debug.print("stok = {d}\n", .{nilai});
} else {
std.debug.print("stok kosong\n", .{});
}
const aman = stok orelse 0;
std.debug.print("aman = {d}\n", .{aman});
std.debug.print("buka = {d}\n", .{stok.?});
stok = null;
const kosong = stok orelse 0;
std.debug.print("kosong = {d}\n", .{kosong});
}stok = 24
aman = 24
buka = 24
kosong = 0Program di atas menunjukkan ketiganya secara berurutan. Blok if menangkap nilai 24 dan mencetaknya, orelse 0 menghasilkan nilai aslinya karena stok tidak null, .? membuka nilai langsung, dan setelah stok = null dijalankan, orelse 0 menjadi satu-satunya jalan yang tersisa. Perhatikan bahwa stok dideklarasikan dengan var karena nilainya diubah menjadi null di tengah program.
# jalankan program dari direktori topik
zig run buka-opsi.zigstok = 24
aman = 24
buka = 24
kosong = 0Perulangan while dengan capture#
Perulangan while dapat menjadikan optional sebagai syaratnya. Selama optional berisi nilai, nilainya ditangkap dan badan perulangan dieksekusi, sedangkan kemunculan null pertama mengakhiri perulangan tanpa perlu pemeriksaan tambahan di dalam badan.
// tidak-dijalankan
var sisa: ?u32 = 3;
while (sisa) |n| : (sisa = if (n > 1) n - 1 else null) {
std.debug.print("melayani nomor {d}\n", .{n});
}
// perulangan berhenti saat sisa menjadi nullEkspresi lanjutan setelah tanda titik dua dijalankan pada setiap akhir iterasi dan menulis kembali sisa. Pada contoh tersebut, nomor antrian dikurangi satu per satu hingga cadangannya habis, dan sisa ditetapkan null sehingga perulangan berhenti dengan sendirinya. Pola ini cocok untuk antrean, pemrosesan bertahap, dan penelusuran yang berhenti pada kegagalan pertama.
Pointer opsional ?*T dan pointer sentinel#
Tipe ?*T menyimpan alamat memori yang boleh tidak ada. Nilai null di sini berarti tidak menunjuk apa pun, dan bentuk ini muncul lazim pada struktur data bertaut serta parameter fungsi yang boleh menerima ketiadaan. Ukuran ?*T sama persis dengan *T, karena alamat nol yang tidak pernah menjadi alamat sah dipakai sebagai wakil null tanpa menambah satu byte pun.
// tidak-dijalankan
const teks: [*:0]const u8 = "akhir";
// teks selalu menunjuk memori, dan byte 0 menandai akhir datanya
var dicari: ?[*:0]const u8 = null;
// dicari dapat menyatakan ketiadaan alamat secara eksplisitPerbedaan dengan pointer sentinel (sentinel-terminated pointer) perlu dicermati. Pointer sentinel seperti [*:0]const u8 selalu menunjuk memori yang ada, dan nilai pengakhirnya adalah bagian dari data, bukan tanda ketiadaan alamat. Ketiadaan hanya dapat dinyatakan dengan membungkusnya menjadi optional seperti ?[*:0]const u8. String pada Zig memakai pola sentinel untuk menandai akhir datanya, sedangkan optional menjawab soal yang berbeda, yaitu ada atau tidaknya alamat yang ditunjuk.
Contoh Lengkap#
Program utuh: pencarian indeks kartu anggota#
Program berikut mencari sebuah nomor kartu dalam array dan melaporkan posisinya. Fungsi cari mengembalikan ?usize, yaitu indeks temuan atau null bila nomor tidak terdaftar, dan pemanggilnya wajib menangani kedua kemungkinan tersebut.
const std = @import("std");
fn cari(data: []const u32, target: u32) ?usize {
for (data, 0..) |nilai, i| {
if (nilai == target) return i;
}
return null;
}
pub fn main() void {
const nomor_kartu = [_]u32{ 1105, 2380, 3007, 4142 };
const indeks = cari(&nomor_kartu, 3007) orelse {
std.debug.print("kartu tidak terdaftar\n", .{});
return;
};
std.debug.print("kartu 3007 pada indeks {d}\n", .{indeks});
if (cari(&nomor_kartu, 9999)) |i| {
std.debug.print("kartu 9999 pada indeks {d}\n", .{i});
} else {
std.debug.print("kartu 9999 tidak terdaftar\n", .{});
}
}kartu 3007 pada indeks 2
kartu 9999 tidak terdaftarFungsi cari menelusuri slice dengan for yang menyediakan indeks, lalu mengembalikan indeks pada kecocokan pertama atau null setelah seluruh data dilewati. Pemanggil pertama memakai orelse dengan blok yang mencetak pesan dan menghentikan main, sehingga indeks setelahnya sudah pasti terbuka. Pemanggil kedua memakai capture if dan jatuh ke cabang else karena 9999 tidak ada dalam daftar.
# jalankan program dari direktori topik
zig run cari-indeks.zigkartu 3007 pada indeks 2
kartu 9999 tidak terdaftarKesalahan umum#
Kesalahan pada optional biasanya berkaitan dengan pembukaan nilai yang tergesa. Tiga kasus berikut paling sering muncul beserta perbaikannya.
.?dipanggil pada optional yang bisa null. Program berhenti dengan panic penggunaan nilai null pada mode Debug. Gunakanorelse, capture if, atau periksa!= nullterlebih dahulu ketika null adalah keadaan yang wajar.- Nilai
?Tditeruskan ke parameter bertipeT. Kompilator menolaknya karena keduanya tipe yang berbeda. Buka nilai terlebih dahulu denganorelseatau capture sebelum diteruskan. - Perulangan while atas optional tidak pernah mencapai null. Tanpa ekspresi lanjutan yang pada akhirnya menetapkan
null, badan perulangan terus berputar. Pastikan ada satu jalur yang menetapkannull, misalnya pada ekspresi lanjutan setelah titik dua.
Ringkasan#
- Tipe
?Tmenyatakan nilai ataunullsecara eksplisit, dengannullberada di luar himpunan nilaiT. .?membuka nilai dan berhenti dengan panic bila null, sedangkanorelsedan capture if menyatakan rencana cadangan secara eksplisit.whileatas optional berhenti padanullpertama, dan capture-nya menyediakan nilai yang sudah terbuka di dalam badan perulangan.?*Tberukuran sama dengan*Tkarenanulldiwakili alamat nol, berbeda dari pointer sentinel yang selalu menunjuk data yang ada.
Referensi#
- Optionals — Zig Language Reference
— Tipe optional,
orelse, dan penangkapan nilai. - Optional-Pointers — Zig Language Reference
— Perbedaan
?*Tdengan pointer sentinel beserta ukurannya.