Setelah error menempuh beberapa lapisan pembungkusan, pertanyaan pemanggil tidak lagi sederhana: “apakah kegagalan ini berakar pada ErrTerlaluPercobaan?” atau “apakah ada ErrorTransfer di suatu tempat di rantai ini?”. Dua fungsi package errors menjawabnya: errors.Is mencari keberadaan sebuah nilai, errors.As mencari dan mengambil sebuah tipe. Pada chapter ini kita akan membedah cara kerja keduanya dan membuktikan mengapa keduanya bukan sekadar type assertion.
Menelusuri Rantai dengan errors.Is #
Fungsi errors.Is(err, target) membandingkan setiap simpul rantai dengan target menggunakan operator ==, lalu turun ke penyebab berikutnya lewat Unwrap() hingga rantai habis. Pada setiap simpul, bila tipe error mengimplementasikan method Is(target error) bool, errors.Is memanggil method tersebut dan memakai hasilnya — standard library memakai mekanisme ini agar error syscall di dalam fs.PathError cocok dengan fs.ErrNotExist tanpa identitas pointer yang sama. Karena errors.Join membentuk rantai bercabang, pencarian errors.Is juga menjelajahi seluruh cabangnya.
Kapan pemanggil memakainya? Biasanya untuk keputusan bercabang: pengguna tidak ditemukan → balas 404; sibuk → coba lagi; selain itu → gagal umum.
Mengambil Tipe dengan errors.As #
errors.As(err, &target) menjelajah rantai dengan cara yang sama, tetapi yang dicocokkan adalah tipe. Argumen keduanya wajib pointer — pointer ke variabel bertipe pointer-ke-tipe-error:
var et *ErrorTransfer
if errors.As(err, &et) {
fmt.Println(et.Rekening, et.Jumlah)
}Bila satu simpul rantai bertipe *ErrorTransfer, fungsi tersebut mengisi et dengan simpul itu dan mengembalikan true. Tipe yang dicari harus mengimplementasikan error — bukan hanya konvensi, melainkan syarat yang diperiksa.
Bukan Sekadar Type Assertion #
Type assertion err.(*ErrorTransfer) memeriksa hanya tipe terluar dari err. Begitu error dibungkus fmt.Errorf, tipe terluarnya menjadi tipe pembungkus milik package fmt, sehingga assertion selalu gagal. errors.As bekerja berbeda: fungsi ini membuka setiap lapisan Unwrap() dan membandingkan tipe di kedalaman berapa pun. Perbedaan keduanya adalah perbedaan “melihat sampul” dan “membongkus seluruh kotak”.
Program Utuh #
Berkas errors-is-as-demo.go mensimulasikan dua lapisan transfer dengan dua jenis kegagalan berbeda:
package main
import (
"errors"
"fmt"
)
// ErrTerlaluPercobaan adalah sentinel untuk limit transaksi.
var ErrTerlaluPercobaan = errors.New("terlalu banyak percobaan")
// ErrorTransfer membawa data transaksi untuk keputusan bisnis.
type ErrorTransfer struct {
Rekening string
Jumlah int
}
func (e *ErrorTransfer) Error() string {
return fmt.Sprintf("transfer %s sebesar Rp%d gagal", e.Rekening, e.Jumlah)
}
func prosesTransfer(rekening string, jumlah int) error {
if jumlah > 5_000_000 {
return fmt.Errorf("validasi jumlah: %w", ErrTerlaluPercobaan)
}
if jumlah < 10_000 {
return &ErrorTransfer{Rekening: rekening, Jumlah: jumlah}
}
return nil
}
func layananTransfer(rekening string, jumlah int) error {
if err := prosesTransfer(rekening, jumlah); err != nil {
return fmt.Errorf("layanan transfer: %w", err)
}
return nil
}
func main() {
err := layananTransfer("BRI-001", 7_500_000)
fmt.Println("A:", err)
fmt.Println(" errors.Is ErrTerlaluPercobaan:", errors.Is(err, ErrTerlaluPercobaan))
err = layananTransfer("BRI-001", 5_000)
fmt.Println("B:", err)
var et *ErrorTransfer
if errors.As(err, &et) {
fmt.Println(" errors.As -> rekening:", et.Rekening, "jumlah:", et.Jumlah)
}
_, ok := err.(*ErrorTransfer)
fmt.Println(" type assertion langsung:", ok)
}Jalankan program dari direktori chapter dengan perintah berikut:
go run errors-is-as-demo.goOutput yang Dihasilkan:
A: layanan transfer: validasi jumlah: terlalu banyak percobaan
errors.Is ErrTerlaluPercobaan: true
B: layanan transfer: transfer BRI-001 sebesar Rp5000 gagal
errors.As -> rekening: BRI-001 jumlah: 5000
type assertion langsung: falseMari kita telaah baris demi baris program di atas:
- Baris 22-24 (
if jumlah > 5_000_000 ...): lapisan bawah membungkus sentinelErrTerlaluPercobaandengan%w; kegagalan jenis ini identitasnya tetap dapat dicari dari luar. - Baris 25-27 (
if jumlah < 10_000 ...): jenis kegagalan kedua berupa*ErrorTransferyang membawa data rekening dan jumlah untuk keputusan bisnis. - Baris 31-36 (
func layananTransfer): lapisan atas membungkus hasil lapisan bawah apa pun tipenya, sehingga kedua jenis kegagalan tiba di pemanggil dalam keadaan terbungkus. - Baris 41 (
errors.Is(err, ErrTerlaluPercobaan)): fungsi menuruni rantailayanan transfer → validasi jumlah → sentineldan menemukan targetnya, meski tipe terluar bukan sentinel tersebut. - Baris 46-49 (
var et *ErrorTransfer; errors.As(err, &et)): target adalah pointer ke*ErrorTransfer; fungsi mengisietdengan simpul bertipe itu sehingga fieldRekeningdanJumlahterbaca langsung. - Baris 51-52 (
err.(*ErrorTransfer)): type assertion biasa gagal (false) karena tipe terluarerradalah pembungkusfmt, bukan*ErrorTransfer— buktierrors.Asdan type assertion tidak saling menggantikan.
Dengan errors.Is dan errors.As, seluruh kegagalan yang bisa dipulihkan sudah terkelola sebagai nilai. Tetapi ada satu jalur kegagalan yang tidak lewat nilai balikan sama sekali: panic. Chapter berikutnya, panic dan recover
, membahas kapan jalur itu sah dan bagaimana menangkapnya.
Referensi #
- Dokumentasi errors: Is — aturan pencarian nilai dalam rantai error.
- Dokumentasi errors: As — aturan pencarian tipe dan syarat target.
- Working with Errors in Go 1.13 — latar belakang desain kedua fungsi ini.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
errors-is-as-demo.go
Unduh berkaspackage main
import (
"errors"
"fmt"
)
// ErrTerlaluPercobaan adalah sentinel untuk limit transaksi.
var ErrTerlaluPercobaan = errors.New("terlalu banyak percobaan")
// ErrorTransfer membawa data transaksi untuk keputusan bisnis.
type ErrorTransfer struct {
Rekening string
Jumlah int
}
func (e *ErrorTransfer) Error() string {
return fmt.Sprintf("transfer %s sebesar Rp%d gagal", e.Rekening, e.Jumlah)
}
func prosesTransfer(rekening string, jumlah int) error {
if jumlah > 5_000_000 {
return fmt.Errorf("validasi jumlah: %w", ErrTerlaluPercobaan)
}
if jumlah < 10_000 {
return &ErrorTransfer{Rekening: rekening, Jumlah: jumlah}
}
return nil
}
func layananTransfer(rekening string, jumlah int) error {
if err := prosesTransfer(rekening, jumlah); err != nil {
return fmt.Errorf("layanan transfer: %w", err)
}
return nil
}
func main() {
err := layananTransfer("BRI-001", 7_500_000)
fmt.Println("A:", err)
fmt.Println(" errors.Is ErrTerlaluPercobaan:", errors.Is(err, ErrTerlaluPercobaan))
err = layananTransfer("BRI-001", 5_000)
fmt.Println("B:", err)
var et *ErrorTransfer
if errors.As(err, &et) {
fmt.Println(" errors.As -> rekening:", et.Rekening, "jumlah:", et.Jumlah)
}
_, ok := err.(*ErrorTransfer)
fmt.Println(" type assertion langsung:", ok)
}