Episode ini membedah modeling data di Go: struct sebagai model data dengan field tags dan method receiver, interface dengan duck typing idiomatik, serta generik modern dengan type constraints, slice dan map generik, dan reusable algorithms.

Program yang berisi banyak variabel terpisah akan cepat kacau. Di sinilah struct dan interface berperan: struct membentuk model data, interface mendefinisikan kontrak perilaku. Ditambah generik yang stabil sejak Go 1.18, kalian bisa menulis algoritma reusable tanpa kehilangan type safety.
Episode 5 membahas ketiga elemen ini secara mendalam: struct dengan field tags dan method receiver, interface dengan duck typing, serta generik modern dengan constraint dan slice/map generik. Ini perangkat modeling data yang akan kalian pakai di semua episode berikutnya.
Struct adalah kumpulan field bernama dengan tipe tertentu. Field tags adalah string metadata di belakang tipe field — tag paling umum dipakai untuk encoding JSON:
package main
import (
"encoding/json"
"fmt"
)
type Pengguna struct {
Nama string `json:"nama"`
Umur int `json:"umur"`
}
func main() {
u := Pengguna{Nama: "Arman", Umur: 30}
data, _ := json.Marshal(u)
fmt.Println(string(data))
}Tag json:"nama" mengontrol nama field saat di-marshal dan di-unmarshal. Opsi omitempty menghilangkan field kosong: json:"umur,omitempty". Konvensi tag yang sama dipakai library lain seperti ORM untuk kolom database. Ada tiga cara membuat instance struct: literal keyed Pengguna{Nama: "Arman"}, literal posisional, dan new. Literal keyed lebih disarankan karena eksplisit.
Method adalah fungsi dengan receiver — tipe yang menjadi pemilik method. Receiver pointer *Pengguna memungkinkan method mengubah field:
type Pengguna struct {
Nama string
Umur int
}
func (p *Pengguna) BertambahUmur() {
p.Umur++
}Aturan konsisten: jika satu method memakai pointer receiver, seluruh method pada tipe itu sebaiknya memakai pointer receiver. Pemakaian yang campur aduk membingungkan dan memicu error "cannot use value as pointer".
Interface adalah kumpulan method. Sebuah tipe secara implisit memenuhi interface jika memiliki semua method yang dideklarasikan — tanpa kata kunci implements. Inilah esensi duck typing idiomatik Go: selama berperilaku seperti bebek, ia adalah bebek.
package main
import "fmt"
type Pengguna struct {
Nama string
Umur int
}
func (p Pengguna) String() string {
return fmt.Sprintf("%s berumur %d", p.Nama, p.Umur)
}
func main() {
var s fmt.Stringer = Pengguna{Nama: "Arman", Umur: 30}
fmt.Println(s)
}Dengan mengimplementasikan String(), tipe Pengguna otomatis memenuhi interface fmt.Stringer dari standard library, dan fmt.Println memakai representasi tersebut.
Sebelum generik, interface{} dipakai untuk tipe apapun. Sejak Go 1.18, alias resminya adalah any. Hati-hati: any menunda pengecekan tipe ke runtime dan menyerahkannya ke pengguna lewat type assertion:
func cetakNilai(v any) {
if angka, ok := v.(int); ok {
fmt.Println("integer:", angka)
}
}Generik memungkinkan fungsi bekerja dengan banyak tipe tanpa duplikasi. Type parameter ditulis dalam kurung siku, dan constraint membatasi tipe yang diizinkan.
package main
import "fmt"
func CetakDua[T any](a, b T) {
fmt.Println(a, b)
}
func main() {
CetakDua(1, 2)
CetakDua("satu", "dua")
}Paket standar menyediakan constraint comparable untuk tipe yang mendukung ==. Untuk kebutuhan khusus, kalian bisa mendefinisikan constraint sendiri:
type Numerik interface {
~int | ~float64
}
func RataRata[T Numerik](data []T) T {
var total T
for _, v := range data {
total += v
}
return total / T(len(data))
}Tanda ~ memperbolehkan tipe alias dengan representasi dasar yang sama. Paket slices dan maps yang stabil sejak Go 1.21 memakai generik untuk operasi umum seperti slices.Sort, slices.Contains, dan maps.Keys — tidak perlu lagi menulis helper sort untuk setiap tipe. Untuk melihat daftar fungsi yang tersedia, jalankan go doc slices dari terminal.
Dalam aplikasi nyata, ketiganya bekerja bersama. Contoh repository generik:
package main
type Penyimpan[T any] struct {
items []T
}
func (s *Penyimpan[T]) Tambah(item T) {
s.items = append(s.items, item)
}
func (s *Penyimpan[T]) Semua() []T {
return s.items
}Pola ini memperlihatkan kekuatan Go: struct membawa state, method receiver mendefinisikan perilaku, dan generik membuatnya reusable untuk tipe apapun. Ini menjadi fondasi pola repository yang akan kalian terapkan saat menghubungkan aplikasi ke database di episode 8.
Episode 5 melengkapi perangkat modeling data kalian: struct dengan field tags untuk serialisasi dan method receiver untuk perilaku, interface dengan duck typing implisit dan any, serta generik modern dengan type parameters, constraint kustom, dan paket slices dan maps generik.
Inti yang harus dibawa pulang:
any adalah alias interface{} untuk tipe apapun.slices dan maps menyediakan algoritma generik standar.Di episode 6 selanjutnya kita akan membahas error handling, testing, dan debugging dasar — idiom error handling dengan error, fmt.Errorf, dan errors.Is atau errors.As, unit test dengan package testing, table-driven tests, dan benchmarking dengan go test. Kualitas kode kalian akan terukur, bukan sekadar asumsi.