Struct mengelompokkan beberapa nilai yang saling terkait jadi satu, masing-masing dengan nama dan tipenya sendiri. Bayangin kartu menu di Warung Bakso Mas Bimo. Tiap kartu punya kolom yang sama: nama, harga, dan pedas atau gak. Struct adalah cetakan kartunya, dan tiap menu adalah satu kartu yang sudah diisi. Mirip object di JavaScript, bedanya kolom-kolomnya ditentukan dulu lewat sebuah tipe, dan gak bisa ditambah sembarangan.
🧪 Tebakan cepat:
salinan := bakso, lalusalinan.Harga = 18000. Berapabakso.Hargasekarang: 15000 atau 18000? Tebak dulu, jawabannya ada di bagian "Struct itu value: disalin utuh".
Mendefinisikan struct
type Menu struct {
Nama string
Harga int
Pedas bool
}
type Menu struct { ... }membuat tipe baru bernamaMenu. Ditulis di luarfunc main, biasanya di atasnya.- Tiap baris di dalamnya adalah field: namanya, lalu tipenya.
- Field yang diawali huruf besar (
Nama,Harga) bisa dipakai dari package lain, termasukencoding/json. Field berhuruf kecil cuma terlihat di package-nya sendiri.
Bikin nilai struct
bakso := Menu{Nama: "Bakso urat", Harga: 15000, Pedas: true}
es := Menu{Nama: "Es teh", Harga: 5000}
var kosong Menu
fmt.Println(bakso) // {Bakso urat 15000 true}
fmt.Println(es) // {Es teh 5000 false}
fmt.Println(kosong) // { 0 false}
Menu{Nama: ..., Harga: ...}disebut struct literal: nama tipenya, lalu kurung kurawal berisiField: nilai. Jadi untuktype Person struct { Name string; Age int }, nilainya ditulisp := Person{Name: "Budi", Age: 25}.- Field yang gak disebut diisi zero value:
es.Pedasjadifalse. var kosong Menumengisi semua field dengan zero value:"",0,false. String kosong gak kelihatan, makanya tercetak{ 0 false}.- Tanpa nama field juga bisa,
Menu{"Mie ayam", 12000, false}, tapi semua field wajib diisi dengan urutan yang pas. Kurang satu, Go menolak dengantoo few values in struct literal of type Menu. Pakai nama field lebih aman. - Field yang gak ada di tipenya juga ditolak:
Menu{Stok: 20}gagal denganunknown field Stok in struct literal of type Menu.
Baca, ubah, dan cetak field
fmt.Println(bakso.Nama, bakso.Harga) // Bakso urat 15000
fmt.Printf("%v\n", bakso) // {Bakso urat 15000 true}
fmt.Printf("%+v\n", bakso) // {Nama:Bakso urat Harga:15000 Pedas:true}
fmt.Printf("%#v\n", bakso) // main.Menu{Nama:"Bakso urat", Harga:15000, Pedas:true}
bakso.Harga = 16000
fmt.Println(bakso.Harga) // 16000
bakso.Namamembaca satu field;bakso.Harga = 16000mengubahnya.%vmencetak nilainya saja, sama denganfmt.Println.%+vikut mencetak nama field-nya, paling berguna waktu mencari bug.%#vmencetak seperti kode Go, lengkap dengan nama tipe dan tanda kutip.
Struct itu value: disalin utuh
bakso := Menu{Nama: "Bakso urat", Harga: 15000, Pedas: true}
salinan := bakso
salinan.Harga = 18000
fmt.Println(bakso.Harga, salinan.Harga) // 15000 18000
salinan := bakso menyalin semua field ke struct baru, sama seperti array dan beda dengan slice. Jadi bakso.Harga tetap 15000. Itu jawaban tebakannya.
Struct sebagai parameter function
func cetakMenu(m Menu) {
fmt.Printf("\"%s\": Rp%d\n", m.Nama, m.Harga)
}
func naikkanHarga(m Menu) {
m.Harga = 20000 // cuma mengubah salinan milik function ini
}
cetakMenu(bakso) // "Bakso urat": Rp15000
cetakMenu(es) // "Es teh": Rp5000
naikkanHarga(bakso)
fmt.Println(bakso.Harga) // 15000
func cetakMenu(m Menu)adalah function yang menerima satu nilaiMenudan menamainyam(detail function ada di lesson berikutnya). Karena isinya mencetak dari field,m.Namadanm.Harga, function ini benar untuk menu apa pun.\"di dalam teks mencetak tanda kutip, dan%sserta%ddiganti nilai string dan angka sesuai urutannya.- Struct yang dikirim ke function juga disalin.
naikkanHargamengubah salinannya, bukanbakso. Cara mengubah aslinya (pointer) ada di lesson Method.
Struct di dalam struct, dan anonymous struct
type Pesanan struct {
Menu Menu
Porsi int
}
ps := Pesanan{Menu: es, Porsi: 2}
fmt.Printf("%+v\n", ps) // {Menu:{Nama:Es teh Harga:5000 Pedas:false} Porsi:2}
fmt.Println(ps.Menu.Nama, ps.Porsi*ps.Menu.Harga) // Es teh 10000
tamu := struct {
Nama string
Meja int
}{Nama: "Sari", Meja: 3}
fmt.Printf("%+v\n", tamu) // {Nama:Sari Meja:3}
- Field boleh bertipe struct lain. Titiknya disambung:
ps.Menu.Nama. tamuadalah anonymous struct: struct yang didefinisikan langsung tanpa memberi nama tipe. Cocok untuk data sekali pakai. Kalau bentuknya dipakai berulang, bikintype.
💡 Field tag untuk JSON
type MenuJSON struct {
Nama string `json:"nama"`
Harga int `json:"harga"`
Pedas bool `json:"pedas,omitempty"`
stok int
}
b, _ := json.Marshal(MenuJSON{Nama: "Es teh", Harga: 5000, stok: 20})
fmt.Println(string(b)) // {"nama":"Es teh","harga":5000}
- Teks dalam backtick sesudah tipe disebut tag. Go sendiri gak memakainya, tapi package seperti
encoding/jsonmembacanya. json:"nama"memberi nama key di JSON.omitemptymelewati field yang berisi zero value, jadipedasyangfalsegak muncul.stokberhuruf kecil, jadiencoding/jsongak bisa melihatnya sama sekali.json.Marshalmengembalikan dua nilai, hasil dan error._membuang error-nya (dibahas di lesson Multiple Return).
⚠️ Jebakan yang sering ketemu
- Mengira mengubah salinan ikut mengubah aslinya. Assign dan parameter function menyalin struct.
- Struct literal tanpa nama field. Begitu tipenya dapat field baru, semua literal posisional rusak. Pakai
Field: nilai. - Field berhuruf kecil hilang di JSON. Huruf besar di depan kalau field-nya harus terlihat dari package lain.
- Teks yang sudah diketik di dalam function. Function yang menerima struct harus mencetak dari field-nya, bukan teks jadi, supaya benar untuk nilai apa pun.
TL;DR: type Menu struct { Nama string; Harga int } membuat tipe dengan field bernama. Nilainya dibuat dengan struct literal Menu{Nama: "Bakso urat", Harga: 15000}, field yang gak disebut berisi zero value, dan var m Menu semuanya zero value. m.Nama membaca dan mengubah field. %v mencetak nilainya, %+v dengan nama field, %#v seperti kode Go. Struct adalah value: assign dan parameter function membuat salinan. Anonymous struct adalah struct tanpa nama tipe, dan tag seperti json:"nama" dibaca package lain.