BAB 8: Konstanta — Nilai yang Tidak Berubah
Pelajari cara kerja konstanta di Go — dari deklarasi dasar, typed vs untyped constants, hingga iota sebagai cara elegan mendefinisikan nilai berurutan.
Di akhir bab sebelumnya, kita menyinggung bahwa variabel memiliki nilai yang bisa berubah kapan saja. Tapi tidak semua data dalam program seharusnya bisa diubah. Nilai pi, jumlah hari dalam seminggu, atau kode status HTTP tidak pernah berubah — dan memang tidak seharusnya berubah. Go menyediakan const untuk menyimpan nilai-nilai semacam ini.
Berbeda dengan bahasa lain, Go punya pendekatan yang cukup unik untuk konstanta: ada yang bertipe eksplisit, ada yang untyped, dan ada mekanisme iota untuk mendefinisikan deretan nilai berurutan secara efisien. Memahami ketiganya akan membuat kamu menulis kode yang lebih ekspresif dan aman.
Deklarasi Dasar
Sintaks const mirip dengan var, tapi ada satu aturan yang tidak bisa dilanggar: konstanta harus diberi nilai saat dideklarasikan. Tidak ada yang namanya konstanta tanpa nilai awal.
// main.go
package main
import "fmt"
func main() {
const nama string = "John"
const pi float64 = 3.14159
const aktif bool = true
fmt.Println("Nama:", nama)
fmt.Println("Pi:", pi)
fmt.Println("Aktif:", aktif)
}
Sama seperti variabel, kamu bisa menghilangkan tipe dan membiarkan Go menyimpulkannya:
// main.go
package main
import "fmt"
func main() {
const gravitasi = 9.81 // float64
const maks = 100 // int
const pesan = "Go!" // string
fmt.Printf("gravitasi: %v (%T)\n", gravitasi, gravitasi)
fmt.Printf("maks: %v (%T)\n", maks, maks)
fmt.Printf("pesan: %v (%T)\n", pesan, pesan)
}
Ini disebut type inference — konsep yang sama seperti saat kamu menggunakan := untuk variabel di bab sebelumnya.
Sekali dideklarasikan, nilai konstanta tidak bisa diubah. Mencoba nama = "Jane" akan membuat compiler langsung menolak — bukan error saat runtime, tapi saat kompilasi.
Typed vs Untyped Constants
Ini bagian yang membuat konstanta Go berbeda dari bahasa lain. Ketika kamu menulis:
const x = 10
x adalah untyped integer constant. Ia belum punya tipe konkret. Baru saat digunakan dalam ekspresi atau di-assign ke variabel, Go menentukan tipenya. Perilaku ini memberi fleksibilitas yang tidak dimiliki oleh variabel biasa:
// main.go
package main
import "fmt"
const angka = 42 // untyped integer
func main() {
var a int = angka // OK
var b int64 = angka // OK, meski angka "terasa" seperti int
var c float64 = angka // OK juga
fmt.Println(a, b, c)
}
Coba ganti const angka = 42 dengan var angka = 42, dan kode di atas akan langsung gagal dikompilasi karena variabel bertipe int tidak bisa langsung di-assign ke int64 atau float64 tanpa konversi eksplisit.
Sebaliknya, typed constant berperilaku seperti variabel — tipenya sudah terkunci:
const angka int = 42 // typed, hanya bisa dipakai di konteks int
Untuk sebagian besar kasus, untyped constant adalah pilihan yang lebih fleksibel dan idiomatis di Go.
Grouped Constants
Ketika kamu punya banyak konstanta yang berkaitan, mendeklarasikannya satu per satu terasa berulang. Go mendukung pengelompokan dengan kurung:
// main.go
package main
import "fmt"
const (
AppName = "My Go App"
Version = "1.0.0"
MaxUsers = 1000
TimeoutSec = 30
)
func main() {
fmt.Printf("Menjalankan %s v%s\n", AppName, Version)
fmt.Printf("Maks pengguna: %d\n", MaxUsers)
fmt.Printf("Timeout: %d detik\n", TimeoutSec)
}
Ada satu perilaku menarik dalam grouped constants: jika kamu mendeklarasikan konstanta tanpa nilai, Go akan mengulang ekspresi dari konstanta di atasnya:
// main.go
package main
import "fmt"
func main() {
const (
a = "konstanta"
b // b == "konstanta"
c = "berbeda"
d // d == "berbeda"
)
fmt.Println(a, b, c, d)
// Output: konstanta konstanta berbeda berbeda
}
Perilaku ini terlihat sederhana, tapi jadi fondasi dari fitur iota yang lebih powerful.
iota: Penomoran Otomatis
iota adalah identifier bawaan Go yang secara otomatis menghasilkan nilai integer berurutan dalam sebuah const block. Nilainya dimulai dari 0 dan bertambah 1 untuk setiap konstanta baru dalam block yang sama.
// main.go
package main
import "fmt"
const (
Minggu = iota // 0
Senin // 1
Selasa // 2
Rabu // 3
Kamis // 4
Jumat // 5
Sabtu // 6
)
func main() {
fmt.Println("Senin:", Senin) // 1
fmt.Println("Jumat:", Jumat) // 5
fmt.Println("Sabtu:", Sabtu) // 6
}
iota bukan sekadar counter sederhana. Kamu bisa menggunakannya dalam ekspresi untuk menghasilkan pola nilai yang lebih kompleks:
// main.go
package main
import "fmt"
const (
_ = iota // 0, dibuang dengan blank identifier
KB float64 = 1 << (10 * iota) // 1 << 10 = 1024
MB // 1 << 20
GB // 1 << 30
TB // 1 << 40
)
func main() {
fmt.Printf("KB = %.0f bytes\n", KB)
fmt.Printf("MB = %.0f bytes\n", MB)
fmt.Printf("GB = %.0f bytes\n", GB)
fmt.Printf("TB = %.0f bytes\n", TB)
}
Output:
KB = 1024 bytes
MB = 1048576 bytes
GB = 1073741824 bytes
TB = 1099511627776 bytes
Perhatikan _ di baris pertama — blank identifier digunakan untuk membuang nilai iota == 0 ketika kamu tidak ingin dimulai dari nol. Setiap const block baru mereset iota ke 0, jadi dua block terpisah tidak saling memengaruhi.
iota sangat berguna untuk mendefinisikan enum-like values di Go. Karena Go tidak punya keyword enum seperti bahasa lain, pola const + iota adalah cara idiomatis menggantinya.
Konstanta untuk Enum-like Values
Salah satu penggunaan paling umum iota adalah mendefinisikan serangkaian status atau kategori yang saling eksklusif. Kombinasikan dengan tipe kustom untuk keamanan tipe yang lebih baik:
// main.go
package main
import "fmt"
type StatusPesanan int
const (
StatusDraft StatusPesanan = iota
StatusDiproses
StatusDikirim
StatusSelesai
StatusDibatalkan
)
func deskripsiStatus(s StatusPesanan) string {
switch s {
case StatusDraft:
return "Pesanan belum disubmit"
case StatusDiproses:
return "Sedang diproses oleh tim"
case StatusDikirim:
return "Dalam perjalanan ke tujuan"
case StatusSelesai:
return "Pesanan telah diterima"
case StatusDibatalkan:
return "Pesanan dibatalkan"
default:
return "Status tidak dikenal"
}
}
func main() {
pesanan := StatusDiproses
fmt.Println(deskripsiStatus(pesanan))
}
Dengan mendefinisikan tipe StatusPesanan, compiler akan menolak jika kamu mencoba memasukkan nilai integer sembarang ke tempat yang mengharapkan StatusPesanan. Ini jauh lebih aman daripada menggunakan string mentah seperti "diproses".
Scope Konstanta
Konstanta mengikuti aturan scope yang sama dengan variabel. Konstanta yang dideklarasikan di luar fungsi bisa diakses dari seluruh package; yang di dalam fungsi hanya berlaku di blok tersebut.
// main.go
package main
import "fmt"
// Package-level constants
const (
NamaAplikasi = "Go Notes"
Versi = "2.1.0"
)
func infoServer() {
const port = 8080 // hanya tersedia di dalam fungsi ini
fmt.Printf("Server berjalan di port %d\n", port)
}
func main() {
fmt.Printf("Selamat datang di %s v%s\n", NamaAplikasi, Versi)
infoServer()
// fmt.Println(port) // error: port tidak ada di scope ini
}
Konstanta di level package umumnya menggunakan PascalCase jika perlu diakses dari package lain, dan camelCase untuk yang bersifat internal.
Latihan
Latihan 1 — Hari dalam seminggu:
Buat konstanta menggunakan iota untuk merepresentasikan hari Senin sampai Minggu, dimulai dari 1. Tulis fungsi yang menerima nilai hari dan mengembalikan string nama harinya.
Latihan 2 — Ukuran file:
Definisikan konstanta KB, MB, GB, dan TB menggunakan iota dan bit shifting. Tulis fungsi formatUkuran(bytes float64) string yang mengembalikan representasi ukuran dalam satuan yang paling tepat.
Latihan 3 — Status tiket:
Buat tipe StatusTiket berbasis int dengan status: Baru, DalamPenanganan, Menunggu, Selesai, Ditutup. Gunakan konstanta dan tulis fungsi yang mengembalikan deskripsi tiap status.
Konstanta sudah memberi kamu cara untuk menyimpan nilai yang terjamin tidak berubah. Tapi sejauh ini, semua yang kita lakukan — menyimpan nilai ke variabel, mendefinisikan konstanta — belum menghasilkan keputusan apa pun. Program kita masih berjalan lurus dari atas ke bawah tanpa percabangan. Di bab berikutnya, kita akan mengubah itu dengan mempelajari operator: alat untuk membandingkan nilai, melakukan perhitungan, dan membuat logika kondisi yang lebih dinamis.