← Kembali ke kursus
Pelajaran kursus · Go
Pelajaran 57

Go Generics

Tulis fungsi dan tipe yang bekerja untuk banyak tipe.

Pahami konsepnya

Sebelum Go 1.18, fungsi serba-guna memakai interface{} dan kehilangan informasi tipe — atau menulis salinan kode per tipe. Generics mengubah itu: daftar parameter tipe ditulis dalam kurung siku, func Jumlah[T int | float64](angka []T) T, dan T menjadi tipe sungguhan di dalam badan fungsi — pemeriksaan kompilator tetap penuh.

T dibatasi lewat constraint: union tipe seperti int | float64, constraint bernama yang berisi kumpulan tipe berurutan, atau comparable untuk tipe yang mendukung ==. Constraint menentukan operasi apa yang boleh dilakukan pada T.

Selain fungsi, tipe juga generik: type Stack[T any] struct{ items []T } — satu definisi stack untuk semua tipe tanpa assertion. Saat memakainya, kompiler menginstansiasi: Jumlah([]int{...}) menyimpulkan T = int otomatis; Stack[string] ditulis eksplisit untuk tipe.

Coba contoh kode

package main

import "fmt"

func Jumlah[T int | float64](angka []T) T {
    var total T
    for _, n := range angka {
        total += n
    }
    return total
}

func main() {
    fmt.Println(Jumlah([]int{1, 2, 3}))          // 6
    fmt.Println(Jumlah([]float64{1.5, 2.5}))     // 4
}

Satu definisi Jumlah melayani dua tipe — total dideklarasikan bertipe T sehingga nilainya nol yang benar per tipe. Pemanggil tak perlu menyebut T: kompiler menyimpulkannya dari argumen. Versi any akan butuh type assertion dan konversi manual di dalam; versi generik tetap type-safe penuh.

Tipe generik: Stack

type Stack[T any] struct {
    items []T
}

func (s *Stack[T]) Push(v T) { s.items = append(s.items, v) }

func (s *Stack[T]) Pop() (T, bool) {
    var nol T
    if len(s.items) == 0 {
        return nol, false
    }
    akhir := s.items[len(s.items)-1]
    s.items = s.items[:len(s.items)-1]
    return akhir, true
}

func main() {
    s := Stack[string]{}
    s.Push("a")
    s.Push("b")
    v, _ := s.Pop()
    fmt.Println(v) // b
}

Stack[T] menyimpan tipe apa pun dengan TIPEnya tetap terjaga — Pop mengembalikan (T, bool), bukan (any, bool), sehingga pemakai tak perlu assertion. Receiver juga membawa parameter tipe: (s *Stack[T]). Ini menggantikan pola interface{} lama untuk struktur data generik.

Constraint berbasis interface

type Number interface {
    ~int | ~int64 | ~float64
}

func RataRata[T Number](angka []T) T {
    var total T
    for _, n := range angka {
        total += n
    }
    return total / T(len(angka))
}

func main() {
    fmt.Println(RataRata([]int{2, 4, 6})) // 4
}

Constraint bernama merangkum keluarga tipe — Number mencakup int, int64, float64 beserta tipe turunannya (tanda ~). Fungsi ber constraint ini menerima semuanya tanpa duplikasi, dan badan fungsinya hanya boleh memakai operasi yang dijamin constraint (di sini: + dan /).

Latihan singkat

Uji: buat Map generik untuk len() dan Queue[T] dengan Enqueue/Dequeue — harus `[2 6 2]` lalu `a b`.