Struct mengelompokkan beberapa field dengan tipe masing-masing menjadi satu nilai utuh, sehingga data yang saling terkait dapat disimpan dan dikirim sebagai satu kesatuan. Topik ini membahas definisi struct beserta zero value-nya, literal dengan nama field dan literal posisi, penyalinan nilai struct saat ditugaskan, serta penyematan struct lain sebagai field. Pembahasan dibatasi pada dasar-dasarnya agar mekanisme utamanya terbaca utuh.
Mendefinisikan struct dan mengisi field#
Definisi struct diawali kata kunci type, diikuti nama tipe, lalu kata kunci struct beserta daftar field di dalam kurung kurawal. Setiap field memuat nama dan tipe, dan nama field harus unik di dalam satu struct. Berbeda dengan array yang elemennya sejenis dan diindeks angka, field struct dapat bertipe berbeda dan diakses lewat namanya. Variabel bertipe struct yang dideklarasikan tanpa nilai awal langsung berisi struct dengan seluruh fieldnya pada zero value masing-masing:
package main
import "fmt"
// Barang menyimpan satu entri stok di gudang.
type Barang struct {
kode string
nama string
stok int
rak string
}
func main() {
var b Barang
fmt.Printf("Zero value : %+v\n", b)
b.kode = "BRG-01"
b.nama = "Buku tulis"
b.stok = 40
b.rak = "R2"
fmt.Printf("Setelah diisi : %+v\n", b)
fmt.Println("Stok barang :", b.stok)
}Zero value : {kode: nama: stok:0 rak:}
Setelah diisi : {kode:BRG-01 nama:Buku tulis stok:40 rak:R2}
Stok barang : 40Verb %+v mencetak nama field bersama nilainya, sedangkan %v tanpa tanda tambah hanya mencetak nilainya, sehingga versi bernama ini jauh lebih mudah dibaca. Field diakses lewat notasi titik dan ditugaskan sebagaimana variabel biasa. Struct yang baru dideklarasikan tanpa nilai awal tetap layak dipakai karena setiap fieldnya sudah berisi nilai yang terdefinisi, kelanjutan dari prinsip zero value yang berguna di seluruh Go.
Field yang bertipe sama boleh dikelompokkan dalam satu baris dengan pemisah koma, seperti kota, jalan string, dan gaya ini dipakai ketika field-fieldnya serumpun. Struct yang hanya dipakai sekali boleh dideklarasikan tanpa nama, yaitu tipe anonim yang ditulis langsung pada deklarasinya. Bentuk var titik struct{ x, y int } membuat variabel bertipe struct yang tidak punya nama tipe, cocok untuk data sementara di dalam satu fungsi. Untuk struct yang dipakai lintas fungsi, tipe bernama tetap menjadi pilihan karena tipe anonim tidak dapat dirujuk ulang di tempat lain.
Program dijalankan dari folder kerja tetap:
# siapkan folder kerja untuk topik ini
mkdir -p /tmp/gotipe/struct
cp barang.go literal.go sematan.go /tmp/gotipe/struct/
cd /tmp/gotipe/struct
# periksa format lalu jalankan programnya
gofmt -l .
go run barang.goZero value : {kode: nama: stok:0 rak:}
Setelah diisi : {kode:BRG-01 nama:Buku tulis stok:40 rak:R2}
Stok barang : 40Literal struct dan penyalinan nilai#
Composite literal dapat mengisi seluruh field sekaligus pada baris pembuatannya. Bentuk dengan nama field menyebut pasangan nama dan nilai secara eksplisit, sedangkan bentuk posisi hanya mendaftar nilainya mengikuti urutan definisi. Bentuk dengan nama field menjadi pilihan utama karena tahan terhadap penambahan field dan tetap terbaca tanpa membuka definisi struct:
package main
import "fmt"
type Siswa struct {
nama string
kelas string
nilai int
}
func main() {
a := Siswa{nama: "Ali", kelas: "9A", nilai: 88}
b := Siswa{"Budi", "9B", 92}
fmt.Printf("%+v\n", a)
fmt.Printf("%+v\n", b)
c := a
c.nilai = 75
fmt.Println("a tetap:", a)
fmt.Println("c salin:", c)
}{nama:Ali kelas:9A nilai:88}
{nama:Budi kelas:9B nilai:92}
a tetap: {Ali 9A 88}
c salin: {Ali 9A 75}Literal posisi wajib memuat seluruh field dalam urutan yang persis sama dengan definisinya, sehingga penambahan satu field mengubah makna setiap argumen lama. Pemeriksa go vet menandai literal posisi untuk struct yang diimpor dari paket lain karena risiko tersebut. Sebagaimana array, dua struct dapat dibandingkan dengan == apabila seluruh fieldnya dapat dibandingkan, dan hasilnya bernilai true bila setiap field berpasangan sama. Baris terakhir program menunjukkan sifat nilai struct, yaitu c := a menyalin seluruh field, sehingga perubahan pada c tidak terlihat oleh a.
Menyematkan struct lain sebagai field#
Struct boleh menyematkan (embed) struct lain sebagai field tanpa nama field, cukup dengan menulis tipe yang disematkan. Nama field kemudian menjadi nama tipenya sendiri, dan field-field dari struct tersemat terpromosi (promoted), yaitu dapat diakses langsung melalui struct pembungkusnya tanpa menulis nama field perantaranya:
package main
import "fmt"
type Alamat struct {
kota string
jalan string
}
type Peserta struct {
nama string
Alamat
}
func main() {
p := Peserta{
nama: "Citra",
Alamat: Alamat{kota: "Bandung", jalan: "Jl. Merdeka 10"},
}
fmt.Printf("%+v\n", p)
fmt.Println("Kota peserta:", p.kota)
fmt.Println("Alamat utuh :", p.Alamat)
}{nama:Citra Alamat:{kota:Bandung jalan:Jl. Merdeka 10}}
Kota peserta: Bandung
Alamat utuh : {Bandung Jl. Merdeka 10}Penulisan p.kota pada contoh bekerja karena field kota milik Alamat terpromosi ke Peserta, sedangkan p.Alamat tetap dipakai ketika seluruh sub-struct dibutuhkan. Nama field terpromosi inilah yang dipakai pula saat mengisi literal, yaitu kunci Alamat: Alamat{...} pada pembentukan p di atas. Struct tersemat memiliki zero value juga, sehingga var p Peserta menghasilkan struct dengan Alamat berisi zero value alamatnya. Apabila struct luar juga memiliki field dengan nama yang sama, yang terpilih adalah field yang posisinya lebih dangkal, sedangkan field yang tersembunyi tetap dapat diakses lewat nama lengkapnya. Aturan yang sama berlaku untuk tipe apa pun yang disematkan, termasuk antarmuka, dan penyematan ini menjadi dasar komposisi pada desain Go.
Kesalahan umum#
- Literal posisi setelah field bertambah. Penambahan satu field membuat argumen literal posisi bergeser makna, dan urutan yang salah dapat lolos kompilasi bila tipenya kebetulan sama. Perbaikannya, gunakan literal dengan nama field agar pergeseran tidak mungkin terjadi.
- Membandingkan struct yang memuat slice dengan
==. Struct yang salah satu fieldnya bertipe slice atau peta tidak dapat dibandingkan dan ditolak kompiler. Perbaikannya, bandingkan field satu per satu sesuai kebutuhan program. - Mengira penugasan struct berbagi data. Seperti array, struct ditugaskan sebagai salinan utuh, sehingga perubahan pada salinan tidak terlihat oleh asalnya. Perbaikannya, gunakan pointer
*Tapabila dua variabel memang harus menunjuk data yang sama.
Ringkasan#
- Struct didefinisikan dengan
typedan daftar field bernama, dan zero value-nya berisi zero value dari setiap field. - Literal dengan nama field lebih tahan perubahan daripada literal posisi yang wajib lengkap dan berurutan.
- Penugasan struct menyalin seluruh fieldnya, bukan berbagi data dengan asalnya.
- Struct yang disematkan mempromosikan fieldnya, sehingga field tersebut dapat diakses langsung dari struct pembungkusnya.
Referensi#
- The Go Programming Language Specification — The Go Authors — Menetapkan definisi struct, field, dan aturan keunikannya.
- The Go Programming Language Specification — The Go Authors — Menetapkan bentuk literal dengan nama field dan literal posisi.
- Effective Go — The Go Authors — Memaparkan penyematan dan mekanisme field terpromosi.