Go tidak punya kata kunci enum. Kamu membangun enum dari dua bagian: sebuah tipe bernama, dan blok const berisi nilai-nilai bertipe itu, yang dinomori dengan iota.
Sunday bernilai 0, dan setiap baris berikutnya bertambah satu. Tipe bernama Weekday inilah yang membuatnya menjadi enum, bukan sekadar daftar angka: isWeekend menyatakan di signature-nya apa yang ia harapkan, dan method bisa ditambahkan ke tipe itu. Output-nya mencetak 1 5 6 karena belum ada yang memberi tahu Go cara menampilkan Weekday sebagai teks. Itu dibahas di bawah.
Cara Kerja iota
iota adalah counter yang disediakan compiler di dalam blok const. Dua aturan menjelaskan setiap trik yang dibangun di atasnya:
iotabernilai sama dengan indeks baris saat ini di dalam blok, dimulai dari 0, dan kembali ke 0 di setiap blokconstyang baru.- Baris tanpa
= expressionmengulang ekspresi dan tipe dari baris sebelumnya, yang dievaluasi denganiotayang baru.
Jadi Monday di atas adalah singkatan dari Monday Weekday = iota, dengan iota yang sekarang bernilai 1. Karena ekspresinya diulang, ekspresi itu bisa berupa ekspresi konstanta apa pun, tidak hanya iota:
iota menghitung baris, bukan nama: dua konstanta di baris yang sama berbagi satu nilai iota, seperti yang ditunjukkan X dan Y.
Mulai dari 1, dan Kenapa Mungkin Tidak Perlu
Variabel bertipe enum yang tidak pernah diisi siapa pun berisi 0, yaitu zero value-nya. Kalau 0 adalah nilai yang sungguhan seperti Sunday, kamu tidak bisa membedakan "pengguna memilih Sunday" dari "field-nya tidak pernah diisi". Tiga perbaikan yang umum:
Opsi 1 paling umum di kode produksi, dan enum Go yang dibuat oleh protobuf mengikutinya (..._UNSPECIFIED = 0). Zero value lalu punya arti yang jujur.
Melewati Nilai
Blank identifier _ memakai satu nilai iota tanpa membuat nama. Pakai untuk meninggalkan celah, misalnya untuk menyesuaikan dengan angka yang ditetapkan sebuah protokol atau untuk memensiunkan sebuah nilai tanpa menomori ulang yang lain:
type Opcode byte
const (
OpContinue Opcode = iota // 0
OpText // 1
OpBinary // 2
_ // 3, reserved
_ // 4, reserved
_ // 5, reserved
_ // 6, reserved
_ // 7, reserved
OpClose // 8
OpPing // 9
OpPong // 10
)
Saat angkanya sudah ditetapkan oleh spesifikasi eksternal, seperti opcode WebSocket ini, menuliskannya secara eksplisit (OpClose Opcode = 8) sering kali lebih jelas daripada menghitung blank identifier. iota dipakai untuk nilai yang angka pastinya tidak kamu pedulikan.
Jangan pernah mengubah urutan atau menyisipkan ke dalam daftar iota yang angkanya disimpan di database, file, atau dikirim lewat jaringan. Menambahkan baris di tengah menggeser setiap nilai setelahnya. Tambahkan nilai baru di akhir, atau tetapkan angkanya secara eksplisit.
Menambahkan Method String
Beri tipe itu method String() string dan fmt akan memakainya untuk %v, %s, dan Println:
Dua detail dalam method itu penting:
- Pemeriksaan batas. Tanpa itu,
Weekday(9).String()akan panic dengan index out of range, dan suatu saat itu pasti terjadi, karena tidak ada yang mencegah pemanggil membuatWeekday(9). int(d)di dalamSprintf. Memformatdsendiri dengan%dtidak masalah, tetapi memformatnya dengan%vakan memanggilString()lagi dan terus berulang sampai stack overflow.
%d tetap mencetak angkanya, jadi kamu mendapat kedua bentuknya: Wednesday is day 3.
Membuat String Otomatis dengan stringer
Untuk daftar yang panjang, tool stringer menulis method itu untukmu:
//go:generate go run golang.org/x/tools/cmd/stringer@latest -type=Weekday
go generate ./...
Tool ini membuat weekday_string.go berisi implementasi String() yang ringkas, ditambah pemeriksaan saat kompilasi yang menggagalkan build kalau konstantanya berubah tanpa dibuat ulang. Flag -linecomment memakai komentar di akhir baris sebagai nama, yang berguna untuk nama yang mengandung spasi.
Memvalidasi Nilai
Enum di Go tidak tertutup. Nilai apa pun dari tipe dasarnya bisa dikonversi ke enum itu, dan konstanta tanpa tipe dikonversi secara implisit:
var d Weekday = 42 // compiles
d = Weekday(userInput) // compiles
Jadi periksa nilai yang datang dari luar kodemu (JSON, database, flag, package lain):
Sentinel colorCount yang tidak diekspor di akhir blok menjaga IsValid tetap benar saat kamu menambahkan warna baru, karena posisinya selalu satu setelah nilai sungguhan yang terakhir.
Switch pada Enum
Enum biasanya dipakai lewat switch. Go tidak memeriksa apakah sebuah switch sudah mencakup setiap nilai, jadi tambahkan default yang melaporkan nilai yang tidak terduga:
func (c Color) Hex() string {
switch c {
case Red:
return "#ff0000"
case Green:
return "#00ff00"
case Blue:
return "#0000ff"
default:
return "#000000"
}
}
Linter pihak ketiga exhaustive (termasuk dalam golangci-lint) melaporkan switch pada tipe enum yang melewatkan sebuah case, yang memberimu sebagian besar manfaat pemeriksaan enum yang lengkap seperti di bahasa lain.
Enum Bit Flag
Saat nilai-nilainya bisa digabung, seperti izin akses, pakai satu bit per nilai dengan 1 << iota:
| menggabungkan flag, & mengujinya, dan &^ (operator AND NOT di Go) menghapusnya. Tipe dasar unsigned adalah pilihan yang tepat di sini: uint8 menampung 8 flag, uint64 menampung 64.
Enum String
Saat nilainya memang disimpan atau dikirim sebagai teks, tipe berbasis string menghindari lapisan konversi:
type Env string
const (
EnvDev Env = "dev"
EnvStaging Env = "staging"
EnvProd Env = "prod"
)
Nilainya tercetak dan terserialisasi dengan mudah dibaca tanpa method String(), dan kolom database berisi "prod", bukan angka yang bergantung pada urutan deklarasi. Kekurangannya: perbandingannya adalah perbandingan string, bit flag tidak mungkin dipakai, dan validasi tetap menjadi tanggung jawabmu, karena Env("banana") juga bisa dikompilasi.
Enum dan JSON
Enum integer diserialisasi sebagai angka secara default. Untuk membaca dan menulis namanya, implementasikan encoding.TextMarshaler dan encoding.TextUnmarshaler. encoding/json memakainya untuk nilai dan untuk key map:
MarshalText punya value receiver sehingga bisa dipakai pada Level maupun *Level; UnmarshalText butuh pointer receiver karena ia mengubah nilainya. Dua method yang sama membuat tipe ini bisa dipakai dengan TextVar dari package flag dan dengan kebanyakan library konfigurasi.
Hal yang Sering Menjebak
- Konversi implisit dari literal. Fungsi yang menerima
Weekdayjuga menerima konstanta tanpa tipe42. Hanya nilai bertipe dari tipe lain yang ditolak. - Lupa menulis tipe di baris pertama. Dalam
const ( Red = iota; Green; Blue ), ketiganya adalah konstanta integer tanpa tipe, bukan nilaiColor, jadi method padaColortidak berlaku untuk mereka. TulisRed Color = iotasupaya ekspresi yang diulang membawa tipenya. - Mengubah urutan enum yang disimpan. Menyisipkan nilai di tengah blok
iotadiam-diam mengubah angka yang sudah tersimpan di tempat lain. - Rekursi di String. Di dalam
String(), jangan pernah memformat receiver dengan%vatau%s. Konversi dulu ke tipe dasarnya.
Pertanyaan yang sering diajukan
Apakah Go punya enum?
Tidak sebagai fitur bahasa. Tidak ada kata kunci enum. Penggantinya yang idiomatis adalah tipe bernama ditambah blok konstanta bertipe, biasanya dinomori dengan iota:
type Color int
const (
Red Color = iota
Green
Blue
)
Tipe itu memberimu signature yang mudah dibaca dan tempat untuk menaruh method seperti String(). Tipe itu tidak mencegah seseorang menulis Color(42), jadi validasi nilai yang datang dari luar.
Apa itu iota di Go?
iota adalah identifier bawaan yang bernilai sama dengan indeks baris saat ini (constant spec) di dalam blok const, dimulai dari 0. Nilainya kembali ke 0 di setiap blok const yang baru. Saat sebuah baris tidak menulis ekspresinya, Go mengulang ekspresi sebelumnya dengan iota berikutnya, dan itulah yang membuat Red = iota; Green; Blue menghasilkan 0, 1, 2.
Bagaimana cara membuat iota mulai dari 1?
Tulis First Kind = iota + 1 di baris pertama, atau lewati nol dengan blank identifier: _ = iota lalu First. Banyak programmer Go justru mempertahankan 0 dan menamainya Unknown atau Invalid, supaya variabel yang belum diinisialisasi (zero value-nya 0) jelas bukan pilihan yang sungguhan.
Bagaimana cara mencetak enum sebagai string di Go?
Beri tipe itu method String() string. fmt memanggilnya untuk %v, %s, dan Println, jadi fmt.Println(Green) mencetak Green, bukan 1. Kamu bisa menulis method itu secara manual dengan switch atau array, atau membuatnya otomatis dengan go run golang.org/x/tools/cmd/stringer@latest -type=Color.
Bagaimana cara mengonversi string ke enum di Go?
Tulis fungsi parse yang mencari string tersebut, biasanya di map[string]Color atau switch, dan mengembalikan error untuk input yang tidak dikenal: func ParseColor(s string) (Color, error). Mengimplementasikan UnmarshalText dengan logika yang sama membuat JSON, flag, dan loader konfigurasi memakainya secara otomatis.