Menu

Generic di Golang: Type Parameter, Constraint, dan Contoh

Generic (Go 1.18+) memungkinkan satu fungsi atau tipe bekerja dengan banyak tipe sambil tetap type-safe. Pelajari type parameter, constraint any, comparable, dan cmp.Ordered, constraint kustom dengan ~, tipe generic, dan kapan sebaiknya tidak memakainya.

Halaman ini dilengkapi editor yang bisa dijalankan. Edit, jalankan, dan lihat output-nya seketika.

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.

ConstraintMengizinkanMemungkinkan kamu memakai
anysetiap tipeassignment, pengiriman, penyimpanan
comparabletipe yang mendukung ====, !=, key map
cmp.Ordered (Go 1.21)integer, float, string<, <=, >, >=, ditambah ==
interface dengan methodtipe yang punya method itumemanggil method-nya
type union seperti ~int | ~float64tepat tipe-tipe ituoperator 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 T adalah cara mengembalikan "tidak ada apa-apa" dari kode generic. Tidak ada T{} atau nil yang bisa dipakai untuk setiap tipe.
  • Instansiasi. Tipe generic harus diinstansiasi sebelum dipakai: Stack[string], bukan Stack. Di dalam method-nya sendiri, receiver-nya ditulis Stack[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 pada any(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.ID tidak 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.Writer lebih 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 daripada func 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 any di tempat yang butuh comparable atau cmp.Ordered. Compiler menolak == atau < pada type parameter any.
  • Lupa ~. Tipe buatan pengguna seperti type ID int gagal memenuhi constraint int biasa.
  • Mengembalikan nil untuk 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, dan cmp lebih 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].

Ilustrasi bahasa pemrograman di Coddy

Belajar coding dengan Coddy

Mulai