Fungsi generik menyusun keluarga fungsi; tipe generik menyusun keluarga tipe data. Container seperti stack, queue, dan set adalah contoh klasiknya: struktur datanya identik, hanya tipe elemen yang berbeda. Chapter ini membangun container Stack[T] lengkap dari deklarasi struct, method dengan receiver generik, hingga konstruktor generik, lalu memperluasnya dengan tipe Pair berparameter ganda dan pemakaian generic type sebagai tipe nilai map. Sintaks receiver pada method adalah bagian yang paling sering salah tulis, jadi kita akan membedahnya dengan cermat.

Deklarasi Struct Generik #

Type parameter pada deklarasi tipe ditulis tepat setelah nama tipe:

go
type Stack[T any] struct {
	data []T
}

Satu deklarasi Stack kini melahirkan keluarga tipe: Stack[int], Stack[string], Stack[Peserta]. Field data bertipe []T berubah mengikuti instansiasi — Stack[int] menyimpan []int, Stack[string] menyimpan []string. Constraint any cukup untuk container murni karena container hanya menyimpan dan mengeluarkan elemen tanpa memeriksa isinya.

Method pada Tipe Generik: Receiver Wajib Menyebut T #

Method untuk tipe generik memakai type parameter milik tipe tersebut, dan sintaks receiver-nya wajib menyebut kurung siku beserta type parameternya:

go
func (s *Stack[T]) Push(nilai T) {
	s.data = append(s.data, nilai)
}

Receiver (s *Stack[T]) — bukan (s *Stack) — karena Stack tanpa type parameter bukan tipe lengkap. Penulisan func (s *Stack) Push(...) ditolak compiler: tipe generik harus selalu dipakai lengkap dengan instansiasinya. Method Push dan Pop di bawah memakai T seperti tipe biasa: parameter nilai T, nilai balik T, dan zero value var kosong T ketika stack kosong.

Pointer receiver dipilih karena Push memodifikasi s.data — aturan value receiver vs pointer receiver yang kita bahas pada chapter Value Receiver dan Pointer Receiver berlaku sama persis pada tipe generik.

Konstruktor Generik #

Konstruktor tipe generik adalah fungsi generik biasa yang mengembalikan instansiasi tipe:

go
func NewStack[T any]() *Stack[T] {
	return &Stack[T]{}
}

Karena tidak menerima satu argumen pun, T tidak bisa disimpulkan — pemanggil wajib menulis NewStack[int]() eksplisit, kasus nyata aturan inferensi dari chapter Generic Function . Konstruktor semacam ini bermanfaat untuk menyiapkan field internal tanpa memaksa pemanggil membangun struct literal.

Dua Type Parameter dan Generic Type sebagai Nilai Map #

Struct generik boleh membawa lebih dari satu type parameter dengan constraint berbeda, seperti Pair kunci-nilai:

go
type Pair[K comparable, V any] struct {
	Kunci K
	Nilai V
}

Constraint comparable pada K tidak wajib untuk deklarasi struct, tetapi membatasi K sejak awal membuat Pair siap dipakai sebagai kunci map — syarat kunci map dari chapter Constraint comparable . Sebagai nilai map, generic type dituliskan lengkap: map[string]Pair[string, int] memetakan string ke pasangan string-int.

Program Demo #

Berkas generictype-demo.go mengoperasikan Stack untuk dua tipe dan Pair sebagai nilai map:

go
package main

import "fmt"

// Stack adalah tipe generik yang menyimpan elemen bertipe T.
type Stack[T any] struct {
	data []T
}

// NewStack adalah konstruktor untuk Stack.
func NewStack[T any]() *Stack[T] {
	return &Stack[T]{}
}

// Receiver WAJIB menyebut type parameter: (s *Stack[T]).
func (s *Stack[T]) Push(nilai T) {
	s.data = append(s.data, nilai)
}

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

func (s *Stack[T]) Len() int {
	return len(s.data)
}

// Pair memakai dua type parameter dengan constraint berbeda.
type Pair[K comparable, V any] struct {
	Kunci K
	Nilai V
}

func main() {
	// Konstruktor tanpa argumen: instansiasi harus eksplisit.
	angka := NewStack[int]()
	angka.Push(10)
	angka.Push(20)
	angka.Push(30)

	v, ok := angka.Pop()
	fmt.Println("Pop angka   :", v, ok)
	fmt.Println("Sisa elemen :", angka.Len())

	teks := NewStack[string]()
	teks.Push("halo")
	teks.Push("go")
	s, _ := teks.Pop()
	fmt.Println("Pop teks    :", s)

	// Generic type sebagai tipe nilai map.
	konfig := map[string]Pair[string, int]{
		"timeout": {Kunci: "detik", Nilai: 30},
	}
	fmt.Println("Timeout     :", konfig["timeout"].Kunci, konfig["timeout"].Nilai)
}

Output yang Dihasilkan:

text
Pop angka   : 30 true
Sisa elemen : 2
Pop teks    : go
Timeout     : detik 30

Mari kita telaah baris demi baris program di atas:

  1. Baris 6-8 (type Stack[T any]): deklarasi struct generik; field data []T mengikuti tipe instansiasi sehingga Stack[int] dan Stack[string] adalah dua tipe berbeda yang tidak bisa saling mengisi.
  2. Baris 11-13 (NewStack): konstruktor generik tanpa argumen mengembalikan *Stack[T] siap pakai; pemanggil menentukan T lewat NewStack[int]() karena inferensi tidak punya petunjuk.
  3. Baris 16-18 (Push): receiver (s *Stack[T]) menyebut type parameter — bentuk wajib untuk method tipe generik; append menambah elemen bertipe T ke s.data.
  4. Baris 20-28 (Pop): nilai balik ganda (T, bool); baris 21 mendeklarasikan var kosong T sebagai zero value yang dikembalikan saat stack kosong, mengikuti idiom “nilai, ok”.
  5. Baris 25-27: elemen terakhir disimpan lalu slice dipangkas s.data[:len(s.data)-1] — pola pop standar: elemen tidak dihapus dari memori, hanya tidak lagi terjangkau.
  6. Baris 42-45: angka := NewStack[int]() membuat stack khusus int; tiga Push mengisi 10, 20, 30, dan Pop mengeluarkan 30 terlebih dahulu — perilaku LIFO (last in, first out).
  7. Baris 58-61 (konfig): map[string]Pair[string, int] memakai generic type sebagai tipe nilai; composite literal {Kunci: "detik", Nilai: 30} diakses lewat konfig["timeout"].Kunci dan .Nilai.

Referensi #

Method Push dan Pop memakai type parameter milik receiver, tetapi satu pertanyaan belum terjawab: bolehkah sebuah method membawa type parameter barunya sendiri? Chapter Limitasi Method pada Generic Type menjawab pertanyaan tersebut beserta sejarah aturannya.

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

generictype-demo.go

Unduh berkas
go
package main

import "fmt"

// Stack adalah tipe generik yang menyimpan elemen bertipe T.
type Stack[T any] struct {
	data []T
}

// NewStack adalah konstruktor untuk Stack.
func NewStack[T any]() *Stack[T] {
	return &Stack[T]{}
}

// Receiver WAJIB menyebut type parameter: (s *Stack[T]).
func (s *Stack[T]) Push(nilai T) {
	s.data = append(s.data, nilai)
}

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

func (s *Stack[T]) Len() int {
	return len(s.data)
}

// Pair memakai dua type parameter dengan constraint berbeda.
type Pair[K comparable, V any] struct {
	Kunci K
	Nilai V
}

func main() {
	// Konstruktor tanpa argumen: instansiasi harus eksplisit.
	angka := NewStack[int]()
	angka.Push(10)
	angka.Push(20)
	angka.Push(30)

	v, ok := angka.Pop()
	fmt.Println("Pop angka   :", v, ok)
	fmt.Println("Sisa elemen :", angka.Len())

	teks := NewStack[string]()
	teks.Push("halo")
	teks.Push("go")
	s, _ := teks.Pop()
	fmt.Println("Pop teks    :", s)

	// Generic type sebagai tipe nilai map.
	konfig := map[string]Pair[string, int]{
		"timeout": {Kunci: "detik", Nilai: 30},
	}
	fmt.Println("Timeout     :", konfig["timeout"].Kunci, konfig["timeout"].Nilai)
}