BAB 7: Tipe Data — Sistem Tipe Statis Go
Pahami sistem tipe data Go yang static — dari integer, float, boolean, string, hingga composite types seperti array, slice, map, dan struct yang jadi fondasi program Go.
Di bab sebelumnya, kamu belajar menyimpan data ke dalam variabel. Tapi ada pertanyaan yang belum dijawab: kenapa 10 dan 10.5 diperlakukan berbeda? Kenapa kamu tidak bisa langsung menjumlahkan angka dan teks? Jawabannya ada di tipe data.
Go adalah bahasa dengan static type system — setiap variabel harus punya tipe yang ditentukan saat kompilasi, bukan saat program berjalan. Ini terasa lebih ketat dibanding Python atau JavaScript, tapi justru itulah yang membuat program Go lebih mudah diprediksi: compiler akan menangkap kesalahan tipe sebelum program sempat dijalankan.
Angka Bulat: Integer
Pilihan tipe paling sering kamu temui adalah int. Ini adalah tipe bilangan bulat default di Go, ukurannya mengikuti platform — 32-bit di sistem 32-bit, 64-bit di sistem 64-bit.
// main.go
package main
import "fmt"
func main() {
umur := 25
jumlahSiswa := 42
suhuKulkas := -18
fmt.Println(umur, jumlahSiswa, suhuKulkas)
}
Go juga menyediakan varian integer dengan ukuran eksplisit ketika kamu perlu kontrol yang lebih ketat atas memori atau range nilai:
| Tipe | Range | Kapan dipakai |
|---|---|---|
int8 | -128 sampai 127 | Data yang nilainya kecil, hemat memori |
int16 | -32.768 sampai 32.767 | Jarang dipakai, protokol lama |
int32 | -2 miliar sampai 2 miliar | Unicode code point (rune) |
int64 | Sangat besar | Timestamp, ID besar |
uint8 | 0 sampai 255 | Data byte, pixel warna |
uint | 0 sampai batas platform | Panjang dan indeks |
Dalam praktik sehari-hari, int sudah cukup untuk hampir semua kebutuhan. Gunakan varian ukuran spesifik hanya ketika kamu punya alasan yang jelas — misalnya bekerja dengan data biner, protokol jaringan, atau memori terbatas.
byte adalah alias untuk uint8, dan rune adalah alias untuk int32. Kamu akan sering melihat keduanya saat bekerja dengan string dan karakter.
Angka Desimal: Float
Untuk angka dengan titik desimal, Go menyediakan dua pilihan: float32 dan float64. Perbedaannya ada di presisi — float64 menyimpan angka dengan akurasi yang jauh lebih tinggi karena menggunakan 64 bit.
// main.go
package main
import "fmt"
func main() {
tinggi := 175.5 // float64 (default)
var berat float32 = 68.3
pi := 3.141592653589793 // float64, presisi penuh
fmt.Printf("Tinggi: %.1f cm\n", tinggi)
fmt.Printf("Berat: %.1f kg\n", berat)
fmt.Printf("Pi: %.10f\n", pi)
}
Saat kamu menulis angka desimal tanpa deklarasi tipe eksplisit, Go selalu menggunakannya sebagai float64. Ini pilihan yang aman karena float64 lebih akurat. Gunakan float32 hanya kalau kamu benar-benar perlu menghemat memori dalam jumlah besar, misalnya array ratusan ribu elemen.
Boolean: Benar atau Salah
Tipe bool hanya punya dua nilai: true dan false. Sederhana, tapi perannya krusial — hampir semua logika kondisional bergantung pada boolean.
// main.go
package main
import "fmt"
func main() {
sudahLogin := true
aksesDitolak := false
// Operator logika: &&, ||, !
bisaAkses := sudahLogin && !aksesDitolak
fmt.Println("Bisa akses:", bisaAkses) // true
suhu := 35.0
puasaAktif := true
perluMinum := suhu > 33 && !puasaAktif
fmt.Println("Perlu minum:", perluMinum) // false
}
Zero value untuk bool adalah false — variabel bool yang dideklarasikan tanpa nilai awal otomatis bernilai false.
String: Teks
String di Go adalah urutan byte yang immutable — sekali dibuat, isinya tidak bisa diubah secara langsung. Untuk membuat string biasa, gunakan tanda kutip ganda. Untuk string yang mengandung baris baru atau karakter khusus tanpa escape, gunakan backtick.
// main.go
package main
import "fmt"
func main() {
nama := "Gopher"
kota := "Bandung"
// Concatenation
sapaan := "Halo, " + nama + "!"
fmt.Println(sapaan)
// Panjang string (dalam byte)
fmt.Println("Panjang nama:", len(nama)) // 6
// Raw string literal — tidak perlu escape
query := `SELECT *
FROM users
WHERE kota = 'Bandung'`
fmt.Println(query)
_ = kota
}
Ada hal penting yang perlu kamu tahu soal len() dan indeks string di Go. Keduanya bekerja pada level byte, bukan karakter.
// main.go
package main
import "fmt"
func main() {
teks := "Halo"
fmt.Println(len(teks)) // 4 — 4 byte, 4 karakter ASCII
// Mengakses byte (bukan karakter)
fmt.Println(teks[0]) // 72 — nilai byte dari 'H'
fmt.Printf("%c\n", teks[0]) // H — tampilkan sebagai karakter
}
String dengan karakter non-ASCII (termasuk huruf beraksara atau emoji) bisa punya lebih banyak byte dari jumlah karakternya. Untuk iterasi per karakter, gunakan for range yang otomatis bekerja per rune, bukan per byte.
Array: Koleksi Berukuran Tetap
Array adalah koleksi elemen dengan tipe yang sama dan ukuran yang tetap. “Tetap” di sini berarti ukurannya ditentukan saat kompilasi dan tidak bisa berubah.
// main.go
package main
import "fmt"
func main() {
var nilai [5]int // [0 0 0 0 0] — zero value
buah := [3]string{"apel", "jeruk", "mangga"}
angka := [...]int{10, 20, 30, 40} // ukuran otomatis dari isinya
nilai[0] = 85
nilai[1] = 92
fmt.Println("Nilai:", nilai)
fmt.Println("Buah:", buah)
fmt.Println("Jumlah angka:", len(angka)) // 4
}
Ukuran array adalah bagian dari tipenya — [3]string dan [5]string adalah tipe yang berbeda dan tidak bisa saling diassign. Ini membuat array kurang fleksibel untuk kebutuhan umum. Dalam praktik, kamu akan lebih sering menggunakan slice.
Slice: Array yang Fleksibel
Slice adalah abstraksi di atas array — ia menyimpan referensi ke array di bawahnya, bukan datanya secara langsung. Yang membuatnya istimewa: ukurannya bisa berubah dinamis menggunakan append.
// main.go
package main
import "fmt"
func main() {
// Membuat slice
var kota []string // nil slice, kosong
provinsi := []string{"Jawa Barat", "Jawa Tengah"}
angka := make([]int, 3) // [0, 0, 0]
// Menambah elemen
kota = append(kota, "Bandung")
kota = append(kota, "Surabaya", "Medan")
fmt.Println("Kota:", kota) // [Bandung Surabaya Medan]
fmt.Println("Provinsi:", provinsi)
fmt.Println("Angka:", angka) // [0 0 0]
}
len dan cap
Setiap slice punya dua properti penting: length (jumlah elemen yang ada) dan capacity (jumlah elemen yang bisa ditampung sebelum array di bawahnya perlu dialokasikan ulang).
// main.go
package main
import "fmt"
func main() {
s := make([]int, 3, 5) // length 3, capacity 5
fmt.Printf("len=%d, cap=%d, isi=%v\n", len(s), cap(s), s)
s = append(s, 10)
fmt.Printf("len=%d, cap=%d, isi=%v\n", len(s), cap(s), s)
s = append(s, 20)
fmt.Printf("len=%d, cap=%d, isi=%v\n", len(s), cap(s), s)
// Ini akan trigger alokasi baru karena kapasitas penuh
s = append(s, 30)
fmt.Printf("len=%d, cap=%d, isi=%v\n", len(s), cap(s), s)
}
Slicing
Kamu bisa memotong slice menggunakan sintaks [awal:akhir]. Elemen yang diambil dimulai dari indeks awal sampai akhir-1.
// main.go
package main
import "fmt"
func main() {
angka := []int{10, 20, 30, 40, 50}
fmt.Println(angka[1:4]) // [20 30 40]
fmt.Println(angka[:3]) // [10 20 30]
fmt.Println(angka[2:]) // [30 40 50]
fmt.Println(angka[:]) // [10 20 30 40 50] — semua elemen
}
Slice yang dibuat dengan slicing berbagi memori yang sama dengan slice asalnya. Mengubah elemen di slice hasil potongan akan mengubah slice asal juga. Gunakan copy() jika kamu butuh salinan yang independen.
Map: Data Berpasangan
Map adalah struktur data yang menyimpan pasangan key-value. Berbeda dari array atau slice yang menggunakan indeks numerik, map menggunakan key bertipe apapun yang comparable.
// main.go
package main
import "fmt"
func main() {
// Membuat map
nilaiSiswa := map[string]int{
"Budi": 85,
"Sari": 92,
"Anton": 78,
}
// Menambah dan mengubah
nilaiSiswa["Rina"] = 88
nilaiSiswa["Budi"] = 90 // update nilai Budi
// Mengakses
fmt.Println("Nilai Sari:", nilaiSiswa["Sari"])
// Menghapus
delete(nilaiSiswa, "Anton")
fmt.Println("Semua nilai:", nilaiSiswa)
}
Comma-ok idiom
Mengakses key yang tidak ada di map tidak menghasilkan error — Go mengembalikan zero value dari tipe value-nya. Untuk membedakan “key ada dengan nilai 0” dan “key tidak ada”, gunakan comma-ok idiom:
// main.go
package main
import "fmt"
func main() {
stok := map[string]int{
"apel": 10,
"jeruk": 0, // ada, tapi stoknya memang 0
}
// Tanpa comma-ok — tidak bisa bedakan ada/tidak ada
fmt.Println(stok["mangga"]) // 0, tapi mangga tidak ada
// Dengan comma-ok
jumlah, ada := stok["apel"]
fmt.Printf("apel: %d, ada: %t\n", jumlah, ada) // 10, true
jumlah, ada = stok["mangga"]
fmt.Printf("mangga: %d, ada: %t\n", jumlah, ada) // 0, false
}
Struct: Data yang Dikelompokkan
Sejauh ini kamu menyimpan satu jenis data per variabel. Bagaimana kalau kamu perlu menyimpan data yang saling berkaitan — nama, umur, dan email seseorang? Di sinilah struct berperan.
// main.go
package main
import "fmt"
type Pengguna struct {
Nama string
Umur int
Email string
}
func main() {
// Struct literal
p1 := Pengguna{
Nama: "Budi",
Umur: 25,
Email: "budi@mail.com",
}
// Mengakses field
fmt.Println(p1.Nama)
fmt.Printf("%s berumur %d tahun\n", p1.Nama, p1.Umur)
// Mengubah field
p1.Umur = 26
fmt.Println("Umur sekarang:", p1.Umur)
}
Struct juga bisa bersarang — field dari sebuah struct bisa bertipe struct lain:
// main.go
package main
import "fmt"
type Alamat struct {
Jalan string
Kota string
}
type Pengguna struct {
Nama string
Umur int
Alamat Alamat
}
func main() {
p := Pengguna{
Nama: "Sari",
Umur: 28,
Alamat: Alamat{
Jalan: "Jl. Merdeka 10",
Kota: "Bandung",
},
}
fmt.Printf("%s tinggal di %s, %s\n", p.Nama, p.Alamat.Jalan, p.Alamat.Kota)
}
Struct akan dibahas lebih mendalam di bab tersendiri — termasuk method dan embedding. Untuk sekarang, cukup pahami struct sebagai cara mengelompokkan data yang saling berkaitan.
Pointer: Variabel yang Menyimpan Alamat
Setiap variabel di Go disimpan di lokasi tertentu di memori. Pointer adalah variabel yang menyimpan alamat memori dari variabel lain, bukan nilainya langsung.
Operator & mengambil alamat sebuah variabel. Operator * membaca nilai yang ada di alamat tersebut.
// main.go
package main
import "fmt"
func main() {
nilai := 42
ptr := &nilai // ptr menyimpan alamat variabel nilai
fmt.Println("Nilai:", nilai) // 42
fmt.Println("Alamat:", ptr) // alamat memori, misal 0xc000018060
fmt.Println("Via pointer:", *ptr) // 42
// Mengubah nilai melalui pointer
*ptr = 100
fmt.Println("Nilai setelah diubah:", nilai) // 100
}
Pointer akan dibahas lebih lengkap di bab tersendiri, termasuk kenapa Go memilih pendekatan pointer yang eksplisit dibanding bahasa lain.
Zero Values
Setiap tipe di Go punya zero value yang aman — nilai default saat variabel dideklarasikan tanpa nilai awal. Ini adalah salah satu keputusan desain Go yang mencegah bug “uninitialized variable”.
| Tipe | Zero value |
|---|---|
int, semua varian integer | 0 |
float32, float64 | 0.0 |
bool | false |
string | "" |
| array | semua elemen zero value |
| slice, map, pointer | nil |
| struct | semua field zero value |
// main.go
package main
import "fmt"
func main() {
var angka int
var teks string
var aktif bool
var harga float64
var daftar []string
fmt.Printf("int: %d\n", angka) // 0
fmt.Printf("string: %q\n", teks) // ""
fmt.Printf("bool: %t\n", aktif) // false
fmt.Printf("float64: %f\n", harga) // 0.000000
fmt.Printf("slice nil: %t\n", daftar == nil) // true
}
Konversi Tipe
Go tidak melakukan konversi tipe secara implisit — kamu harus melakukannya secara eksplisit. Ini mencegah bug halus yang sering terjadi di bahasa lain ketika tipe dikonversi secara diam-diam.
// main.go
package main
import "fmt"
func main() {
var umur int = 25
var tinggi float64 = 175.5
// Tidak bisa langsung dijumlahkan
// total := umur + tinggi // compile error!
// Harus konversi eksplisit
total := float64(umur) + tinggi
fmt.Printf("Total: %.1f\n", total) // 200.5
// int ke string — perlu fmt.Sprintf, bukan string(angka)
teks := fmt.Sprintf("%d", umur)
fmt.Println("Teks:", teks) // "25"
// string ke []byte dan sebaliknya
kalimat := "Halo Go"
bytes := []byte(kalimat)
kembali := string(bytes)
fmt.Println(bytes) // [72 97 108 111 32 71 111]
fmt.Println(kembali) // Halo Go
}
string(25) tidak menghasilkan "25". Ia menghasilkan karakter dengan Unicode code point 25, yang bukan karakter yang dapat dicetak. Gunakan fmt.Sprintf("%d", angka) atau package strconv untuk konversi angka ke string.
Latihan
-
Buat program yang menyimpan data lima kota beserta jumlah penduduknya menggunakan map. Tampilkan kota dengan jumlah penduduk terbesar.
-
Buat struct
Produkdengan fieldNama(string),Harga(float64), danStok(int). Buat slice yang berisi tiga produk berbeda, lalu hitung total nilai stok (harga dikali stok) untuk seluruh produk. -
Buat slice angka dari 1 sampai 10 menggunakan
appenddalam loop. Cetak elemen-elemen di posisi genap menggunakan slicing dan iterasi. -
Deklarasikan variabel
suhubertipefloat64dengan nilai36.6. Konversikan keintdan cetak selisih antara nilai asli dan hasil konversi untuk menunjukkan efek pemotongan desimal.
Kamu sudah mengenal semua tipe data dasar yang ada di Go. Tapi variabel dan tipe saja belum cukup untuk membuat program yang berguna — kamu perlu cara untuk menyimpan nilai yang tidak boleh berubah sama sekali. Di bab berikutnya, kita akan membahas konstanta, dan kenapa Go punya cara yang cukup unik untuk mendefinisikannya lewat iota.