Kegagalan pada Zig dimodelkan sebagai nilai yang dikembalikan, bukan sebagai mekanisme yang menyela jalur program secara tersembunyi. Topik ini membahas error set sebagai tipe yang memuat nama-nama kegagalan, error union !T yang memuat nilai sukses atau error, kata kunci try untuk menyebarkan kegagalan, serta catch untuk menangkapnya. Setelah membacanya, fungsi yang dapat gagal dideklarasikan dengan jelas, dan pemanggilnya wajib memutuskan nasib setiap kegagalan sebelum program dapat dikompilasi.
Konsep#
Error set dan error union#
Error set adalah tipe yang isinya berupa nama-nama kegagalan, ditulis dengan sintaks error{...} mirip dengan enum, dan nilainya cukup untuk menyatakan penyebab gagal tanpa membawa data tambahan. Suatu fungsi dapat mengembalikan nilai sukses atau kegagalan melalui error union, ditulis sebagai SetError!TipeSukses, misalnya ErrorTarik!u32 untuk fungsi yang mengembalikan sisa saldo atau salah satu kegagalan tarik saldo. Di dalam fungsi tersebut, jalur sukses mengembalikan nilai biasa, sedangkan jalur gagal mengembalikan error.NamaKegagalan.
// tidak-dijalankan
const ErrorTarik = error{ SaldoTidakCukup, RekeningTutup };Program penarikan tabungan berikut memakai ketiga bentuk tersebut sekaligus, dari definisi error set hingga pencocokan error di pemanggil.
const std = @import("std");
const ErrorTarik = error{ SaldoTidakCukup, RekeningTutup };
fn tarik(saldo: u32, jumlah: u32) ErrorTarik!u32 {
if (saldo == 0) {
return error.RekeningTutup;
}
if (jumlah > saldo) {
return error.SaldoTidakCukup;
}
return saldo - jumlah;
}
fn prosesTarikan(saldo: u32, jumlah: u32) ErrorTarik!u32 {
const sisa = try tarik(saldo, jumlah);
std.debug.print("penarikan {d} berhasil, sisa saldo {d}\n", .{ jumlah, sisa });
return sisa;
}
fn jelaskan(err: ErrorTarik) []const u8 {
return switch (err) {
error.SaldoTidakCukup => "saldo tidak mencukupi",
error.RekeningTutup => "rekening sudah ditutup",
};
}
pub fn main() void {
_ = prosesTarikan(500000, 200000) catch |err| {
std.debug.print("penarikan ditolak: {s}\n", .{jelaskan(err)});
return;
};
_ = prosesTarikan(0, 100000) catch |err| {
std.debug.print("penarikan ditolak: {s}\n", .{jelaskan(err)});
return;
};
}penarikan 200000 berhasil, sisa saldo 300000
penarikan ditolak: rekening sudah ditutupPanggilan pertama berhasil dan mencetak sisa saldonya, sedangkan panggilan kedua menerima error RekeningTutup dari fungsi tarik. Fungsi jelaskan mencocokkan error dengan switch, dan pencocokannya wajib lengkap atas seluruh anggota error set, sehingga else tidak diperlukan. Nilai error juga dapat dicetak langsung dengan penentu {}, dan hasilnya berupa tulisan error.RekeningTutup yang menggabungkan prefiks error dengan nama anggotanya.
try menyebar, catch menangkap#
Kata kunci try dipakai saat pemanggil tidak berniat menangani kegagalan sendiri, karena try f() singkatan dari f() catch |err| return err, yaitu kegagalan langsung disebarkan ke pemanggil yang lebih atas. Kata kunci catch sebaliknya dipakai saat kegagalan hendak ditangani di tempat, baik dengan nilai cadangan, dengan percabangan, maupun dengan pencocokan nama errornya. Ketika error dibawa keluar fungsi, pemanggilnya yang wajib menutup kasusnya, dan rantai seperti ini berlanjut sampai ada pihak yang benar-benar menangani.
const std = @import("std");
const GalatSensor = error{ SensorMati, PembacaanGagal };
fn bacaSuhu(id: u8) GalatSensor!i16 {
if (id == 2) {
return error.SensorMati;
}
if (id > 3) {
return error.PembacaanGagal;
}
const tabel = [_]i16{ 27, 29, 0, 31 };
return tabel[id];
}
pub fn main() void {
const suhu1 = bacaSuhu(2) catch 25;
std.debug.print("sensor 2: {d} derajat, memakai nilai cadangan\n", .{suhu1});
if (bacaSuhu(3)) |c| {
std.debug.print("sensor 3: {d} derajat\n", .{c});
} else |err| {
std.debug.print("sensor 3 gagal: {}\n", .{err});
}
}sensor 2: 25 derajat, memakai nilai cadangan
sensor 3: 31 derajatPembacaan sensor kedua gagal, tetapi catch 25 menggantikan hasil gagal itu dengan nilai cadangan 25 derajat sehingga program tetap berjalan. Pembacaan ketiga ditangani dengan bentuk if yang bercabang dua, yaitu cabang |c| menerima nilai suksesnya, sedangkan cabang |err| menerima errornya. Bentuk percabangan ini cocok ketika kedua jalur sama pentingnya, sedangkan catch lebih ringkas untuk nilai cadangan dan try lebih ringkas untuk penyebaran.
Contoh Lengkap#
Error adalah nilai biasa#
Zig tidak memiliki exception, yaitu mekanisme yang melempar kegagalan melewati banyak fungsi sekaligus dan membawa stack trace otomatis. Sebaliknya, error adalah bagian dari tipe kembalian fungsi, sehingga jalur gagalnya terlihat pada tanda tangan fungsi dan tidak ada cabang tersembunyi yang melompati kode di tengah jalan. Kegagalan yang diteruskan dengan try tetap bergerak sebagai nilai kembalian dari satu fungsi ke fungsi lain, dan rantainya dapat diikuti dengan membaca deklarasi setiap fungsi di jalur panggilan.
Error set juga dapat digabung dan diinferensi. Operator || menggabungkan dua error set menjadi satu, sedangkan tanda ! tanpa error set di depannya menyatakan error set inferensi, yaitu himpunan yang isinya dirangkum otomatis dari seluruh jalur gagal pada badan fungsi.
// tidak-dijalankan
const GalatBaca = error{Habis} || error{TidakAda};
fn rekap() !void {
try baca();
try tulisRekap();
}Error set inferensi mempersingkat tanda tangan fungsi perantara yang hanya meneruskan kegagalan milik fungsi lain. Kompilator tetap menghitung anggotanya secara tepat, sehingga pemanggil yang menangkap dengan switch hanya perlu menangani error yang memang mungkin terjadi pada jalur itu.
Kesalahan umum#
Kesalahan pertama adalah memanggil fungsi ber-error union tanpa menangani hasilnya. Kompilator menolak programnya dengan pesan error: error union is discarded disertai catatan consider using 'try', 'catch', or 'if'. Abaikan hasilnya secara sadar dengan _ = f() hanya bila kegagalan memang tidak berdampak, dan berikan komentar yang menjelaskan alasannya.
Kesalahan kedua adalah memperlakukan error sebagai peristiwa yang memerlukan pencetakan segera di setiap lapisan fungsi. Pencetakan di lapisan perantara menghasilkan pesan yang berulang untuk satu kegagalan yang sama, sedangkan lapisan perantara sebaiknya hanya meneruskan dengan try dan menyerahkan pelaporan kepada pemanggil akhir. Satu titik tangani menghasilkan pesan yang rapi dan mudah dihitung.
Ringkasan#
- Error set memuat nama-nama kegagalan, sedangkan error union
SetError!Tmenyatakan fungsi yang mengembalikan nilai sukses atau error. trymenyebarkan kegagalan kepada pemanggil, sedangkancatchmenanganinya dengan nilai cadangan, percabanganifbercabang dua, atau pencocokan nama errornya.- Pencocokan error dengan
switchwajib lengkap atas seluruh anggota error set, dan error yang dicetak tampil sebagaierror.Nama. - Error tidak membawa stack trace otomatis karena bergerak sebagai nilai kembalian, dan error set dapat digabung dengan
||maupun diinferensi dengan!tanpa set.
Referensi#
- Error-Set-Type — Zig Language Reference — Definisi error set, penggabungannya, dan error set inferensi.
- Error-Union-Type — Zig Language Reference
— Sintaks
!Tbeserta pemakaiantrydancatch. - Switching-on-Errors — Zig Language Reference — Pencocokan nilai error dengan switch.