81. Pengenalan Generics & Type Parameters
Memahami konsep Generics di Go 1.18+ untuk membuat kode yang re-usable tanpa mengorbankan Type Safety.
Apa itu Generics?
Generics (diperkenalkan pada Go 1.18) memungkinkan kita menulis fungsi dan tipe data yang bekerja dengan berbagai macam tipe data tanpa harus menduplikasi kode atau menggunakan interface{} (any) yang mengorbankan type safety saat kompilasi.
- Sebelum Go 1.18: Harus buat `SumInt(a, b int)` dan `SumFloat(a, b float64)` terpisah, atau pakai `interface{}` dengan tipe assertion yang rentan runtime panic.
- Sesudah Go 1.18: Buat 1 fungsi generik `Sum[T Number](a, b T)` yang aman dan efisien!
Sintaks Dasar Type Parameter:
func FunctionName[T Constraint](param T) T { ... }
Contoh program
package main
import (
"fmt"
)
// Fungsi Generik Sederhana dengan constraint 'any'
func CetakData[T any](val T) {
fmt.Printf("Value: %v | Tipe: %T\n", val, val)
}
func main() {
fmt.Println("=== DEMO GOLANG GENERICS ===")
CetakData[string]("Halo Golang Generics!")
CetakData[int](2026)
CetakData[bool](true)
}
82. Generic Functions & Type Inference
Membuat fungsi generik dan memahami bagaimana kompilator Go secara otomatis menginferensi tipe data parameter.
Type Inference pada Generic Functions
Saat memanggil fungsi generik, kita tidak selalu wajib menuliskan parameter tipe secara eksplisit seperti Sum[int](10, 20). Kompilator Go cukup cerdas untuk secara otomatis mendeteksi tipe data dari argumen yang diberikan (Type Inference).
// Pemanggilan Eksplisit: result := Add[int](5, 10) // Pemanggilan dengan Type Inference (Dianjurkan): result := Add(5, 10) // Kompilator otomatis tahu T adalah int
Contoh program
package main
import (
"fmt"
)
// Fungsi generik mengembalikan elemen pertama slice
func GetFirst[T any](slice []T) T {
if len(slice) == 0 {
var zero T
return zero
}
return slice[0]
}
func main() {
numbers := []int{10, 20, 30}
names := []string{"Eko", "Budi", "Joko"}
// Menggunakan Type Inference otomatis
fmt.Println("First number:", GetFirst(numbers)) // T di-infer sebagai int
fmt.Println("First name:", GetFirst(names)) // T di-infer sebagai string
}
83. Generic Structs & Data Structures
Membangun struktur data generik yang fleksibel seperti Generic Response, Stack, dan Queue.
Generic Structs
Selain fungsi, struct juga dapat dideklarasikan menggunakan parameter tipe. Fitur ini sangat populer dalam pembuatan standar response API atau struktur data umum (Stack, Queue, Linked List).
type Response[T any] struct {
Code int `json:"code"`
Message string `json:"message"`
Data T `json:"data"`
}
Dengan struct generik di atas, Data dapat diisi dengan struct `User`, slice `[]Product`, atau tipe data apa pun tanpa perlu interface{}.
Contoh program
package main
import (
"fmt"
)
// Struct Generic Data Response
type Response[T any] struct {
Status int
Data T
}
type User struct {
Name string
}
func main() {
// Response dengan data bertipe User
userRes := Response[User]{
Status: 200,
Data: User{Name: "Eko Khannedy"},
}
// Response dengan data bertipe slice string
listRes := Response[[]string]{
Status: 200,
Data: []string{"Go", "Java", "Python"},
}
fmt.Println("User Response:", userRes.Data.Name)
fmt.Println("List Response:", listRes.Data)
}
84. Type Sets & Custom Constraints
Membatasi opsi tipe data generik menggunakan Type Sets interface dengan operator Union (|).
Constraint Tipe Data (Type Sets)
Kadang kita ingin membatasi bahwa fungsi generik hanya boleh menerima angka (misalnya `int`, `float64`), bukan `string` atau `bool`. Di Go, ini dilakukan dengan membuat Interface Constraint menggunakan operator union (|).
type Number interface {
int | int32 | int64 | float32 | float64
}
func Sum[T Number](a, b T) T {
return a + b
}
Contoh program
package main
import (
"fmt"
)
// Custom Type Set Constraint
type Number interface {
int | float64
}
func Penjumlahan[T Number](a, b T) T {
return a + b
}
func main() {
fmt.Println("Penjumlahan Int:", Penjumlahan(10, 20))
fmt.Println("Penjumlahan Float:", Penjumlahan(5.5, 4.3))
// Penjumlahan("a", "b") // ERROR saat kompilasi! string bukan bagian dari Number
}
85. Comparable Constraint & Map Keys
Memahami constraint bawaan `comparable` untuk tipe data yang bisa dibandingkan dengan `==` dan `!=`.
Built-in Constraint: comparable
Go menyediakan constraint bawaan bernama comparable. Constraint ini mencakup semua tipe data yang mendukung operator perbandingan sama dengan (==) dan tidak sama dengan (!=), seperti boolean, angka, string, pointer, channel, dan struct yang komparabel.
- Digunakan saat mencari posisi elemen di slice/array.
- Digunakan sebagai constraint tipe data Key pada Map (karena Key map di Go wajib bisa dibandingkan).
Contoh program
package main
import (
"fmt"
)
// Fungsi pencarian index elemen dalam slice
func FindIndex[T comparable](slice []T, target T) int {
for i, item := range slice {
if item == target { // Membutuhkan constraint 'comparable'
return i
}
}
return -1
}
func main() {
names := []string{"Eko", "Budi", "Joko"}
fmt.Println("Index 'Budi':", FindIndex(names, "Budi"))
fmt.Println("Index 'Andi':", FindIndex(names, "Andi"))
}
86. Type Approximation Operator (~)
Menggunakan tilde (~) agar constraint generik mendukung custom type alias (underlying type).
Operator Approximation (~)
Jika kita membuat custom alias type seperti type Age int, variabel bertipe Age secara default tidak memenuhi constraint int biasa.
Untuk mengizinkan semua tipe data yang memiliki underlying type sama, kita menggunakan operator tilde (~) di depan nama tipe pada interface constraint.
type IntConstraint interface {
~int // Mendukung int biasa DAN custom type seperti 'type Age int'
}
Contoh program
package main
import (
"fmt"
)
type Age int // Custom type dengan underlying type int
type IntegerConstraint interface {
~int // Menggunakan approximation operator ~
}
func PrintDouble[T IntegerConstraint](val T) {
fmt.Println("Hasil Double:", val*2)
}
func main() {
var normalInt int = 25
var myAge Age = 30
PrintDouble(normalInt)
PrintDouble(myAge) // Berhasil karena ada simbol ~int
}
87. Generic Interfaces & Method Constraints
Merancang interface generik dan aturan penerapan method pada tipe data generik di Go.
Generic Interfaces
Interface di Go dapat memiliki type parameter. Hal ini berguna untuk membuat abstraksi seperti Getter[T], Repository[T], atau Processor[T].
type Repository[T any] interface {
Save(entity T) error
FindById(id string) (T, error)
}
Method dari struct generik tidak boleh mendeklarasikan type parameter baru sendiri; method harus menggunakan type parameter yang dideklarasikan pada struct pemiliknya.
Contoh program
package main
import (
"fmt"
)
type Getter[T any] interface {
Get() T
}
type ValueHolder[T any] struct {
value T
}
func (v ValueHolder[T]) Get() T {
return v.value
}
func main() {
var holder Getter[string] = ValueHolder[string]{value: "Data Rahasia"}
fmt.Println("Isi Holder:", holder.Get())
}
88. Package constraints & cmp (Standard Utilities)
Memanfaatkan package bantu standar `cmp` dan `constraints` untuk operasi pembandingan dan pengurutan.
Package Utility Generics
Untuk mempermudah penggunaan Generics, Go menyediakan package standar seperti cmp (mulai Go 1.21) serta eksperimental paket golang.org/x/exp/constraints.
Constraint `cmp.Ordered`:
cmp.Ordered adalah constraint bawaan yang mencakup semua tipe data yang dapat dibandingkan dengan operator order (<, <=, >, >=), yaitu angka dan string.
import "cmp"
func GetMax[T cmp.Ordered](a, b T) T {
if a > b {
return a
}
return b
}
Contoh program
package main
import (
"fmt"
)
// Simulasi cmp.Ordered (tipe yang bisa dibandingkan dengan < dan >)
type Ordered interface {
~int | ~float64 | ~string
}
func Max[T Ordered](a, b T) T {
if a > b {
return a
}
return b
}
func main() {
fmt.Println("Max Int (10 vs 25):", Max(10, 25))
fmt.Println("Max String ('Apple' vs 'Banana'):", Max("Apple", "Banana"))
}