Fungsi generic
Type parameter ditulis di dalam kurung siku sebelum parameter biasa. Masing-masing punya constraint yang menyatakan tipe apa saja yang diizinkan.
T dan U disimpulkan dari argumennya, jadi kamu jarang menulis Map[int, string](...). Inferensi bekerja dari argumen fungsi; Go tidak bisa menyimpulkan tipe yang hanya muncul di hasil. Untuk func Zero[T any]() T, kamu harus menulis Zero[int]().
Sebelum Go 1.18, fungsi ini harus ditulis sekali untuk setiap tipe, atau menerima []interface{} dan kehilangan type safety.
Constraint
Constraint adalah sebuah interface. Constraint membatasi tipe yang bisa dipakai dan, sebagai gantinya, memberi tahu compiler operasi apa saja yang diizinkan pada nilai bertipe itu.
| Constraint | Mengizinkan | Memungkinkan kamu memakai |
|---|---|---|
any | setiap tipe | assignment, pengiriman, penyimpanan |
comparable | tipe yang mendukung == | ==, !=, key map |
cmp.Ordered (Go 1.21) | integer, float, string | <, <=, >, >=, ditambah == |
| interface dengan method | tipe yang punya method itu | memanggil method-nya |
type union seperti ~int | ~float64 | tepat tipe-tipe itu | operator yang didukung semuanya |
Dengan any alih-alih comparable, v == target gagal dikompilasi: invalid operation: v == target (incomparable types in type set). Constraint-lah yang membuat operator itu sah.
Dua helper ini sebenarnya sudah ada: slices.Index dan slices.Max. Package standar slices, maps, dan cmp sudah mencakup sebagian besar kebutuhan generic sehari-hari, jadi periksa ketiganya sebelum menulis milikmu sendiri.
Constraint kustom dengan type union dan ~
Constraint bisa mendaftar tipe-tipe dengan |. Operator yang diizinkan adalah operator yang didukung oleh setiap tipe dalam daftar itu.
Tilde itu penting. ~int64 berarti "tipe apa pun yang tipe dasarnya adalah int64", jadi tipe bernama Cents diterima. Kalau ditulis sebagai int64 biasa, Sum([]Cents{...}) gagal dengan Cents does not satisfy Number (possibly missing ~ for int64 in Number).
Interface yang berisi type union hanya bisa dipakai sebagai constraint, tidak pernah sebagai tipe variabel biasa. var n Number tidak bisa dikompilasi.
Package golang.org/x/exp/constraints punya constraint siap pakai Integer, Float, dan Signed. Package ini berada di luar standard library, jadi runner di sini tidak bisa meng-import-nya; cmp.Ordered adalah yang standar.
Tipe generic
Struct, slice, map, dan tipe lain juga bisa punya type parameter. Method pada tipe generic memakai parameter dari receiver-nya.
Dua detail di contoh ini muncul terus-menerus:
- Zero value dari T.
var zero Tadalah cara mengembalikan "tidak ada apa-apa" dari kode generic. Tidak adaT{}ataunilyang bisa dipakai untuk setiap tipe. - Instansiasi. Tipe generic harus diinstansiasi sebelum dipakai:
Stack[string], bukanStack. Di dalam method-nya sendiri, receiver-nya ditulisStack[T].
Go 1.24 menambahkan type alias generic: type Set[T comparable] = map[T]struct{} sekarang bisa dikompilasi.
Batasan yang akan kamu temui
- Tidak ada type parameter pada method.
func (s *Stack[T]) Map[U any](...)tidak diizinkan. Pakai fungsi di level atas. - Tidak ada spesialisasi. Kamu tidak bisa menulis implementasi terpisah untuk
T = string. Type switch padaany(v)di dalam fungsi adalah cara mengakalinya, dan biasanya menjadi tanda bahwa generic bukan alat yang tepat. - Tidak ada constraint operator berdasarkan nama. Kamu tidak bisa mengatakan "tipe apa pun yang punya method
+"; kamu mendaftar tipe-tipenya dalam sebuah union. - Akses field lewat constraint tidak berfungsi. Meskipun setiap tipe dalam union punya field
ID,v.IDtidak diizinkan. Pakai method di dalam constraint sebagai gantinya.
Kapan sebaiknya tidak memakai generic
Generic cocok untuk tipe container (stack, set, cache, tree) dan algoritma atas slice dan map yang melakukan hal yang sama untuk setiap tipe elemen. Generic tidak cocok di semua tempat.
- Hanya satu tipe konkret. Kalau fungsinya hanya pernah dipanggil dengan
[]User, versi generic hanya menambah beban membaca dan tidak memberi apa-apa lagi. - Perilaku yang berbeda per tipe. Untuk itulah interface ada. Fungsi yang menerima
io.Writerlebih sederhana daripada fungsi yang menerima[T io.Writer], dan bekerja sama baiknya. - Menggantikan parameter interface.
func Print[T fmt.Stringer](v T)tidak lebih baik daripadafunc Print(v fmt.Stringer).
Pedoman dari tim Go sendiri: tulis dulu kodenya untuk tipe tertentu, dan pakai type parameter saat kamu menyadari dirimu menulis kode yang sama untuk kedua kalinya dengan hanya tipenya yang berubah.
Kesalahan umum
- Memakai
anydi tempat yang butuhcomparableataucmp.Ordered. Compiler menolak==atau<pada type parameterany. - Lupa
~. Tipe buatan pengguna sepertitype ID intgagal memenuhi constraintintbiasa. - Mengembalikan
niluntuk type parameter. Tidak diizinkan kecuali constraint-nya membatasi T ke tipe yang mirip pointer. Kembalikan zero value. - Menulis helper generic yang sudah ada. Periksa
slices,maps, dancmplebih dulu.
Pertanyaan yang sering diajukan
Apakah Go punya generic?
Ya, sejak Go 1.18 (Maret 2022). Fungsi dan tipe bisa mendeklarasikan type parameter di dalam kurung siku: func Map[T, U any](s []T, f func(T) U) []U. Setiap type parameter punya constraint, yaitu interface yang menjelaskan apa yang harus didukung tipe tersebut.
Apa bedanya any dan comparable di generic Go?
any mengizinkan setiap tipe, tetapi hampir tidak ada yang bisa kamu lakukan dengan nilainya selain meng-assign, mengirim, dan menyimpannya. comparable hanya mengizinkan tipe yang mendukung == dan !=, dan itulah yang kamu butuhkan untuk key map atau pemeriksaan kesamaan. Untuk < dan >, pakai cmp.Ordered.
Apa arti tilde (~) di constraint Go?
~T berarti "tipe apa pun yang tipe dasarnya adalah T". ~int cocok dengan int dan juga type Celsius int. Tanpa tilde, int di dalam constraint hanya cocok dengan int itu sendiri, jadi tipe buatan pengguna akan ditolak.
Apakah method di Go bisa punya type parameter?
Tidak bisa. Method bisa memakai type parameter dari tipe receiver-nya (func (s *Stack[T]) Push(v T)), tetapi tidak bisa mendeklarasikan type parameter baru. Tulis fungsi generic di level atas sebagai gantinya: func Map[T, U any](s *Stack[T], f func(T) U) *Stack[U].