← Semua pembelajaran / Go Nol → Enterprise
Fase 1 · Tipe, Interface & Error

Generic — dan kapan sebaiknya tidak

Generic menyelesaikan satu masalah nyata: menulis fungsi yang sama untuk banyak tipe tanpa kehilangan pemeriksaan compiler. Ia bukan pengganti interface, dan bukan tempat memindahkan kebiasaan dari bahasa lain.

Sumber asli go.dev Resmi Rangkuman ~7 menit baca

Intisari

  • Sintaksnya: func Peta[T, U any](s []T, f func(T) U) []U. Parameter tipe di dalam kurung siku.
  • any, comparable, dan cmp.Ordered menutupi hampir semua kebutuhan batasan tipe.
  • Pakai generic saat algoritmanya sama untuk banyak tipe. Pakai interface saat perilakunya berbeda.
  • Generic tidak menggantikan io.Reader: kalau tipe-tipe itu punya method, interface tetap jawabannya.
  • Go 1.26 melonggarkan aturan sehingga tipe generic boleh menyebut dirinya sendiri di daftar parameter tipe.

Bentuk dasarnya

// T adalah parameter tipe; "any" adalah batasannya (tanpa batasan).
func Pertama[T any](s []T) (T, bool) {
	var nol T
	if len(s) == 0 {
		return nol, false
	}
	return s[0], true
}

n, ok := Pertama([]int{1, 2, 3})       // T disimpulkan: int
s, ok := Pertama([]string{"a", "b"})   // T disimpulkan: string

Perhatikan var nol T: dalam fungsi generic kamu tidak bisa menulis 0 atau "", karena tipenya belum diketahui. Nilai nol dari parameter tipe selalu ditulis begitu.

Batasan tipe

BatasanMengizinkanDari paket
anyTipe apa punbawaan
comparableYang bisa dibandingkan dengan == (kunci map)bawaan
cmp.OrderedYang bisa dibandingkan dengan <: angka dan stringcmp
Interface methodTipe yang punya method tersebutmilikmu
Union: ~int | ~stringDaftar tipe tertentu; ~ berarti termasuk tipe bernama di atasnyamilikmu
import "cmp"

func Maks[T cmp.Ordered](s []T) T {
	hasil := s[0]
	for _, v := range s[1:] {
		if v > hasil {
			hasil = v
		}
	}
	return hasil
}

// Batasan buatan sendiri. Tilde (~) membuat "type Rupiah int64" ikut masuk.
type Angka interface {
	~int | ~int64 | ~float64
}

func Jumlah[T Angka](s []T) T {
	var total T
	for _, v := range s {
		total += v
	}
	return total
}

Jumlah([]Rupiah{25000, 15000})   // bekerja berkat ~int64

Yang benar-benar berguna di aplikasi web

// 1. Respons API terbungkus — hilangkan belasan struct yang isinya sama
type Halaman[T any] struct {
	Data      []T    `json:"data"`
	Berikut   string `json:"berikutnya,omitempty"`
	Total     int    `json:"total"`
}

func kirim[T any](w http.ResponseWriter, status int, v T) error {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(status)
	return json.NewEncoder(w).Encode(v)
}

// 2. Nilai opsional untuk PATCH — bedakan "tidak dikirim" dari "dikirim null"
type Opsional[T any] struct {
	Nilai T
	Ada   bool
}

// 3. Cache bertipe, tanpa type assertion di setiap pemakaian
type Cache[K comparable, V any] struct {
	mu   sync.RWMutex
	data map[K]V
}

func (c *Cache[K, V]) Ambil(k K) (V, bool) {
	c.mu.RLock()
	defer c.mu.RUnlock()
	v, ok := c.data[k]
	return v, ok
}

Kapan generic adalah jawaban yang salah

Kalau kamu…Yang sebenarnya kamu butuhkan
Menulis [T any] lalu langsung switch v.(type)Interface, atau fungsi terpisah
Punya satu tipe yang memakainyaTulis untuk tipe itu saja. Generalisasikan saat pemakai kedua muncul
Membuat Repository[T] generik untuk semua entitasRepository konkret per domain — kuerinya memang berbeda
Membuat Map, Filter, Reduce sendiriPerulangan for biasa; ia lebih mudah dibaca di Go
Ingin "menghilangkan duplikasi" dua fungsi yang miripDuplikasi kecil lebih murah daripada abstraksi yang salah

Pedoman resmi tim Go: tulis kodenya dulu secara konkret. Kalau badan fungsinya ternyata identik untuk beberapa tipe — bukan cuma bentuknya — barulah angkat jadi generic. Kalau badannya berbeda per tipe, yang kamu butuhkan interface.

Generic tidak menggantikan interface

// ❌ tidak ada gunanya: tidak ada perilaku yang dibagi
func Tulis[T io.Writer](w T, b []byte) { w.Write(b) }

// ✅ interface biasa, dan lebih fleksibel
func Tulis(w io.Writer, b []byte) { w.Write(b) }

Aturan pembeda yang mudah diingat: generic untuk tipe data, interface untuk perilaku. Kalau yang berbeda antar tipe adalah apa yang dilakukannya, itu interface. Kalau yang berbeda cuma apa isinya, itu generic.

Biaya yang perlu diketahui

Perubahan di Go 1.26: larangan tipe generic menyebut dirinya sendiri di daftar parameter tipe dicabut, sehingga pola seperti type Adder[A Adder[A]] interface { Add(A) A } sekarang sah. Ini melonggarkan batasan yang selama ini menghalangi beberapa pola API bertipe kuat.

Latihan: tulis func Kelompokkan[T any, K comparable](s []T, kunci func(T) K) map[K][]T dan pakai untuk mengelompokkan slice Produk berdasarkan kategorinya. Lalu coba tulis fungsi yang sama sebagai method dari sebuah struct dengan parameter tipe sendiri — baca pesan error compiler-nya, dan ingat batasan itu.

Rangkuman ini sengaja dipangkas ke bagian yang dipakai di roadmap. Buka sumber aslinya saat kamu butuh detail lengkap atau referensi parameter.