Belajar Golang dari Nol #4: Struct dan Method, Cara Go Menyusun Data
Di Belajar Golang dari Nol #3 kita sudah belajar membungkus logika ke dalam function, mengembalikan lebih dari satu nilai, dan menangani error pertama kita. Sekarang saatnya naik satu tingkat. Kalau function adalah cara Go menyusun logika, maka struct adalah cara Go menyusun data. Di bagian ini kita akan kenalan dengan struct, method, dan pola constructor yang dipakai hampir di semua project Go sungguhan.
Kenapa Kita Butuh Struct
Bayangkan kamu bikin program kasir sederhana. Satu produk punya nama, harga, dan stok. Kalau cuma pakai variabel biasa, kodenya jadi seperti ini.
var namaProduk string = "Kopi Arabika"
var hargaProduk int = 25000
var stokProduk int = 10
Untuk satu produk masih aman. Tapi begitu ada produk kedua, kamu harus bikin namaProduk2, hargaProduk2, stokProduk2. Produk ketiga, tambah lagi. Datanya tercerai berai, padahal ketiganya jelas satu paket. Nama, harga, dan stok itu milik produk yang sama.
Struct menyelesaikan masalah ini. Anggap saja struct itu seperti formulir. Formulir pendaftaran punya kolom nama, alamat, dan nomor telepon. Kolomnya sudah ditentukan dari awal. Setiap orang yang mendaftar mengisi formulir yang sama, tapi isinya beda. Struct persis begitu. Kamu mendefinisikan kolomnya sekali, lalu bisa membuat banyak isian dari cetakan yang sama.
Deklarasi Struct dan Cara Membuat Instance
Mendefinisikan struct di Go pakai kata kunci type dan struct. Kolomnya disebut field. Setiap field punya nama dan tipe data.
package main
import "fmt"
type Produk struct {
Nama string
Harga int
Stok int
}
func main() {
kopi := Produk{
Nama: "Kopi Arabika",
Harga: 25000,
Stok: 10,
}
fmt.Println(kopi.Nama)
fmt.Println(kopi.Harga)
kopi.Stok = 15
fmt.Println(kopi.Stok)
}
Beberapa hal yang perlu kamu perhatikan dari kode di atas.
type Produk structhanya mendefinisikan cetakannya. Belum ada data apa pun di sini.kopi := Produk{...}membuat isian nyata dari cetakan itu. Ini yang disebut instance.- Saat mengisi, kita menulis nama field-nya secara eksplisit, seperti
Nama: "Kopi Arabika". Ini cara yang paling aman dan paling mudah dibaca. Biasakan pakai gaya ini. - Membaca dan mengubah field pakai tanda titik.
kopi.Namauntuk membaca,kopi.Stok = 15untuk mengubah.
Satu catatan yang nyambung ke materi bagian 2 soal zero value. Kalau kamu menulis var kosong Produk tanpa mengisi apa pun, setiap field otomatis berisi zero value tipenya, jadi Nama berisi string kosong dan Harga serta Stok berisi nol.
Struct di Dalam Struct
Field sebuah struct tidak harus tipe dasar. Dia boleh berupa struct lain. Ini berguna waktu datamu memang bertingkat. Contohnya data pelanggan yang punya alamat.
type Alamat struct {
Jalan string
Kota string
}
type Pelanggan struct {
Nama string
Alamat Alamat
}
func main() {
budi := Pelanggan{
Nama: "Budi",
Alamat: Alamat{
Jalan: "Jl. Merdeka No. 10",
Kota: "Purwokerto",
},
}
fmt.Println(budi.Alamat.Kota)
}
Cara aksesnya tinggal dirantai pakai titik. budi.Alamat.Kota artinya ambil field Alamat milik budi, lalu ambil field Kota di dalamnya. Sederhana, tapi pola ini yang nanti kamu pakai untuk memodelkan data dunia nyata seperti pesanan yang punya pelanggan, pelanggan yang punya alamat.
Method: Function yang Menempel di Struct
Sejauh ini struct kita cuma tempat menyimpan data. Padahal data biasanya punya perilaku. Produk bisa dihitung harga diskonnya. Di bagian 3 kamu sudah bisa menulis function seperti ini.
func HitungDiskon(p Produk, persen int) int {
potongan := p.Harga * persen / 100
return p.Harga - potongan
}
Kode itu jalan. Tapi Go punya cara yang lebih rapi, namanya method. Method adalah function biasa yang diberi satu tambahan di depan namanya, disebut receiver. Receiver menentukan struct mana yang memiliki method itu.
func (p Produk) HitungDiskon(persen int) int {
potongan := p.Harga * persen / 100
return p.Harga - potongan
}
Perhatikan bagian (p Produk) sebelum nama function. Itulah receiver. Artinya, method HitungDiskon milik struct Produk, dan di dalam method kita bisa mengakses datanya lewat p.
Cara memanggilnya juga berubah. Dari HitungDiskon(kopi, 10) menjadi kopi.HitungDiskon(10). Bedanya terasa waktu kode mulai besar. Dengan method, kamu bertanya ke datanya langsung. Ketik kopi. di editor, dan semua kemampuan produk muncul sebagai saran. Data dan perilakunya jadi satu paket, tidak berserakan sebagai function lepas.
Pointer Receiver vs Value Receiver
Sekarang bagian yang paling sering bikin pemula bingung. Tenang, kita bahas pelan pakai contoh. Coba tebak output kode ini.
func (p Produk) TambahStokSalah(jumlah int) {
p.Stok = p.Stok + jumlah
}
func (p *Produk) TambahStokBenar(jumlah int) {
p.Stok = p.Stok + jumlah
}
func main() {
kopi := Produk{Nama: "Kopi Arabika", Harga: 25000, Stok: 10}
kopi.TambahStokSalah(5)
fmt.Println(kopi.Stok)
kopi.TambahStokBenar(5)
fmt.Println(kopi.Stok)
}
Outputnya:
10
15
Panggilan pertama tidak mengubah apa pun. Kenapa bisa begitu. Receiver (p Produk) disebut value receiver. Saat method dipanggil, Go membuat salinan dari kopi, lalu method bekerja pada salinan itu. Stok salinannya memang bertambah, tapi begitu method selesai, salinan itu dibuang. kopi yang asli tidak tersentuh.
Receiver (p *Produk) dengan tanda bintang disebut pointer receiver. Method tidak menerima salinan, tapi semacam alamat yang menunjuk ke kopi yang asli. Jadi perubahan stoknya benar-benar nempel.
Kamu belum perlu paham teori pointer secara mendalam sekarang. Pointer akan kita bahas khusus di bagian mendatang. Untuk saat ini, pegang satu aturan praktis ini saja.
- Method mau mengubah data di struct: pakai pointer receiver,
(p *Produk). - Method cuma membaca data, seperti menghitung atau menampilkan: value receiver,
(p Produk), sudah cukup.
Kalau masih ragu, banyak programmer Go memilih pointer receiver untuk semua method di satu struct supaya konsisten. Itu juga pilihan yang wajar.
Constructor Pattern ala Go
Ada satu masalah kecil yang belum kita selesaikan. Tidak ada yang melarang orang membuat produk dengan data ngawur, misalnya Produk{Nama: "", Harga: -5000}. Go tidak punya constructor bawaan seperti bahasa lain. Sebagai gantinya, komunitas Go punya kebiasaan: buat function biasa dengan awalan New yang tugasnya membuat struct sekaligus memvalidasi isinya.
Di sinilah materi error dari bagian 3 kepakai lagi. Constructor mengembalikan dua nilai, struct dan error.
func NewProduk(nama string, harga int, stok int) (Produk, error) {
if nama == "" {
return Produk{}, errors.New("nama produk tidak boleh kosong")
}
if harga <= 0 {
return Produk{}, errors.New("harga harus lebih dari nol")
}
return Produk{Nama: nama, Harga: harga, Stok: stok}, nil
}
Kalau data valid, kita kembalikan struct yang sudah terisi dan error nil. Kalau tidak valid, kita kembalikan struct kosong plus error yang menjelaskan masalahnya. Pemanggilnya wajib mengecek error dulu sebelum memakai struct-nya, persis pola if err != nil yang sudah kamu latih di bagian 3. Dengan pola ini, semua produk yang lolos dari NewProduk dijamin datanya masuk akal.
Latihan: Sistem Stok Mini
Sekarang kita gabungkan semuanya. Struct, method dengan pointer receiver, constructor dengan validasi, dan error. Kita bikin sistem stok mini untuk satu produk. Ketik ulang program ini, jangan copas, supaya tanganmu ikut hafal.
package main
import (
"errors"
"fmt"
)
type Produk struct {
Nama string
Harga int
Stok int
}
func NewProduk(nama string, harga int, stok int) (Produk, error) {
if nama == "" {
return Produk{}, errors.New("nama produk tidak boleh kosong")
}
if harga <= 0 {
return Produk{}, errors.New("harga harus lebih dari nol")
}
if stok < 0 {
return Produk{}, errors.New("stok awal tidak boleh negatif")
}
return Produk{Nama: nama, Harga: harga, Stok: stok}, nil
}
func (p *Produk) TambahStok(jumlah int) error {
if jumlah <= 0 {
return errors.New("jumlah tambahan harus lebih dari nol")
}
p.Stok = p.Stok + jumlah
return nil
}
func (p *Produk) KurangiStok(jumlah int) error {
if jumlah <= 0 {
return errors.New("jumlah pengurangan harus lebih dari nol")
}
if jumlah > p.Stok {
return fmt.Errorf("stok tidak cukup: sisa %d, diminta %d", p.Stok, jumlah)
}
p.Stok = p.Stok - jumlah
return nil
}
func (p Produk) Info() string {
return fmt.Sprintf("%s | harga %d | stok %d", p.Nama, p.Harga, p.Stok)
}
func main() {
kopi, err := NewProduk("Kopi Arabika", 25000, 10)
if err != nil {
fmt.Println("gagal membuat produk:", err)
return
}
fmt.Println(kopi.Info())
err = kopi.TambahStok(5)
if err != nil {
fmt.Println("gagal tambah stok:", err)
}
fmt.Println(kopi.Info())
err = kopi.KurangiStok(8)
if err != nil {
fmt.Println("gagal kurangi stok:", err)
}
fmt.Println(kopi.Info())
err = kopi.KurangiStok(100)
if err != nil {
fmt.Println("gagal kurangi stok:", err)
}
fmt.Println(kopi.Info())
_, err = NewProduk("", 25000, 10)
if err != nil {
fmt.Println("gagal membuat produk:", err)
}
}
Jalankan dengan go run main.go. Outputnya seperti ini.
Kopi Arabika | harga 25000 | stok 10
Kopi Arabika | harga 25000 | stok 15
Kopi Arabika | harga 25000 | stok 7
gagal kurangi stok: stok tidak cukup: sisa 7, diminta 100
Kopi Arabika | harga 25000 | stok 7
gagal membuat produk: nama produk tidak boleh kosong
Baca outputnya baris per baris dan cocokkan dengan kodenya.
- Stok awal 10, tambah 5 jadi 15, kurangi 8 jadi 7. Method
TambahStokdanKurangiStokpakai pointer receiver, jadi perubahannya nempel. - Waktu kita minta 100 padahal sisa 7, method menolak dan mengembalikan error. Perhatikan baris setelahnya: stok tetap 7, tidak berubah jadi minus.
- Percobaan membuat produk tanpa nama juga ditolak oleh
NewProduk. Data yang tidak valid tidak pernah sempat jadi struct yang dipakai program.
Sebagai latihan tambahan, coba tambahkan method HitungDiskon dari bab sebelumnya ke program ini. Lalu coba tambahkan field Kategori ke struct Produk dan lihat bagian mana saja yang perlu kamu sesuaikan.
Rangkuman dan Bagian Selanjutnya
Hari ini kamu sudah pegang fondasi penting Go.
- Struct mengelompokkan data yang berhubungan ke satu tempat, seperti formulir dengan kolom yang sudah ditentukan.
- Field diakses dan diubah pakai tanda titik, dan struct bisa berisi struct lain.
- Method adalah function dengan receiver, sehingga data dan perilakunya jadi satu paket.
- Mau mengubah data, pakai pointer receiver. Cuma membaca, value receiver cukup.
- Constructor
NewProdukmemastikan struct selalu lahir dengan data yang valid.
Pola yang barusan kamu tulis ini bukan sekadar latihan. Struct dengan method dan validasi seperti ini juga fondasi yang kami pakai saat membangun sistem aplikasi berbasis Go untuk klien.
Di bagian 5 kita akan bahas Slice dan Map: Kumpulan Data di Go. Di sana sistem stok mini ini akan naik kelas, dari satu produk menjadi daftar banyak produk. Sampai ketemu di bagian berikutnya.
Artikel Lainnya di Kategori Golang
Golang 27 Juli 2026
Belajar Golang dari Nol #1: Kenalan dengan Go dan Cara Installnya
Kenalan dengan Go: bahasa apa, siapa pemakainya, cara installnya di Linux, Windows, macOS, sampai program pertama. Panduan pemula tanpa ribet.
Baca Artikel
Golang 28 Juli 2026
Belajar Golang dari Nol #2: Variabel, Tipe Data, dan Zero Value
Lanjutan seri Go pemula: tiga cara deklarasi variabel, tipe data dasar, zero value, konstanta, konversi tipe, plus latihan program hitung belanja.
Baca Artikel
Golang 29 Juli 2026
Belajar Golang dari Nol #3: Function, Multiple Return, dan Error
Lanjutan seri Golang pemula: belajar function, multiple return, konvensi error, pola if err != nil, dan variadic lewat latihan kalkulator diskon.
Baca ArtikelIngin Membaca Artikel Lainnya?
Temukan lebih banyak insight dan tips tentang teknologi dan bisnis digital.
Lihat Semua Artikel