Konstanta #
Konstanta di Go adalah lebih dari sekadar “variabel yang tidak bisa diubah.” Di balik keyword const yang tampak sederhana, ada sistem tipe yang unik — untyped constants — yang membuat konstanta Go berperilaku sangat berbeda dari bahasa lain. Dan ada iota, sebuah fitur kecil yang memungkinkan kamu mendefinisikan enum yang ekspresif, bit flags, dan deretan nilai yang berurutan tanpa boilerplate sama sekali. Memahami konstanta Go secara mendalam berarti memahami mengapa const pi = 3.14 bisa dipakai sebagai float32 sekaligus float64 tanpa konversi eksplisit — sesuatu yang tidak mungkin dilakukan variabel biasa.
const vs var — Lebih dari Sekadar “Tidak Bisa Diubah” #
Perbedaan mendasar antara const dan var bukan hanya soal mutability:
// var — nilai ditentukan dan dialokasikan saat runtime
var maxRetry = 3
maxRetry = 5 // ✓ valid — bisa diubah
// const — nilai HARUS bisa dievaluasi saat kompilasi
const maxRetry = 3
// maxRetry = 5 // ✗ compile error: cannot assign to maxRetry
Kata kunci di sini adalah “saat kompilasi” (compile-time). Nilai konstanta harus diketahui sepenuhnya oleh compiler sebelum program dijalankan. Ini berarti kamu tidak bisa menetapkan hasil pemanggilan fungsi sebagai konstanta:
import "time"
// ANTI-PATTERN: compile error — time.Now() adalah fungsi runtime
const startTime = time.Now() // ✗ tidak valid
// BENAR: ini valid karena time.Duration adalah operasi aritmatika konstanta
const timeout = 30 * time.Second // ✓ valid — 30 * konstanta numerik
Keuntungan compile-time evaluation bukan hanya keamanan — compiler bisa menggunakan nilai konstanta untuk optimasi. Kode yang menggunakan konstanta sering lebih efisien karena compiler bisa “melipat” ekspresi konstanta langsung ke dalam instruksi mesin tanpa alokasi memori.
Deklarasi Konstanta #
Konstanta Tunggal #
const pi = 3.14159265358979323846
const greeting = "Selamat datang di Go"
const maxConnections = 100
const debugMode = false
Blok Konstanta #
Untuk mendefinisikan banyak konstanta sekaligus, gunakan blok const (...). Ini cara yang direkomendasikan untuk konstanta yang berkaitan satu sama lain:
const (
// Konfigurasi server
DefaultHost = "localhost"
DefaultPort = 8080
DefaultTimeout = 30 // dalam detik
// Batas aplikasi
MaxPageSize = 100
MaxFileSize = 10 * 1024 * 1024 // 10 MB dalam bytes
MaxRetry = 3
// Versi aplikasi
AppName = "MyGoApp"
AppVersion = "2.1.0"
APIVersion = "v2"
)
Konstanta dengan Tipe Eksplisit #
const (
Pi float64 = 3.14159265358979323846
E float64 = 2.71828182845904523536
Phi float64 = 1.61803398874989484820 // golden ratio
)
type Weekday int
const Monday Weekday = 1
Typed vs Untyped Constants — Fitur Paling Unik Go #
Ini adalah konsep yang tidak ada padanannya di kebanyakan bahasa lain, dan memahaminya membuka banyak hal tentang cara Go bekerja.
Untyped Constants #
Ketika kamu menulis const x = 42, konstanta x adalah untyped — ia belum punya tipe konkret. Sebaliknya, ia punya “default type” yang digunakan jika tidak ada konteks tipe lain. Tapi jika ada konteks tipe yang jelas, ia akan “menyesuaikan diri” ke tipe tersebut:
const x = 42 // untyped integer constant
var i int = x // ✓ x dipakai sebagai int
var i32 int32 = x // ✓ x dipakai sebagai int32
var i64 int64 = x // ✓ x dipakai sebagai int64
var f64 float64 = x // ✓ x dipakai sebagai float64 (42.0)
var c128 complex128 = x // ✓ x dipakai sebagai complex128 (42+0i)
// Bandingkan dengan variabel biasa:
var y int = 42
var f float64 = y // ✗ compile error: cannot use y (type int) as type float64
Inilah kenapa konstanta di Go jauh lebih fleksibel dari variabel biasa — mereka tidak “terkunci” ke satu tipe.
Typed Constants #
Jika kamu menyertakan tipe eksplisit, konstanta menjadi typed dan kehilangan fleksibilitas untyped:
const typedX int = 42
var i int = typedX // ✓
var i64 int64 = typedX // ✗ compile error: cannot use typedX (type int) as type int64
var f float64 = typedX // ✗ compile error
Kapan Menggunakan Typed vs Untyped? #
// Gunakan UNTYPED untuk konstanta matematika dan magic numbers
// yang mungkin dipakai dalam berbagai konteks tipe
const (
KB = 1024
MB = 1024 * KB
GB = 1024 * MB
)
var fileSize int64 = 10 * GB // ✓ GB dipakai sebagai int64
var bufferSize int = 4 * KB // ✓ KB dipakai sebagai int
var displaySize float64 = 1.5 * GB // ✓ GB dipakai sebagai float64
// Gunakan TYPED untuk enum dan konstanta yang punya tipe spesifik
type Direction int
const (
North Direction = iota
South
East
West
)
// Typed membuat compiler mencegah penggunaan sembarangan:
// func move(d Direction) {}
// move(1) // ✗ compile error: cannot use 1 (untyped int constant) as Direction
Presisi Tak Terbatas Untyped Numeric Constants #
Ini adalah keajaiban kecil yang jarang diperhatikan: untyped numeric constants di Go menyimpan nilai dengan presisi tak terbatas. Mereka tidak dibatasi oleh lebar bit seperti int64 atau float64 — nilainya disimpan sebagai angka matematika murni selama compile-time.
// Konstanta ini menyimpan pi dengan 20 digit desimal penuh
const pi = 3.14159265358979323846
// Saat dipakai, presisi disesuaikan dengan tipe target
var f32 float32 = pi // 3.1415927 (7 digit, presisi float32)
var f64 float64 = pi // 3.141592653589793 (15-17 digit, presisi float64)
// Operasi aritmatika pada konstanta juga menggunakan presisi tak terbatas
const bigNumber = 1 << 100 // 2^100 — tidak ada overflow!
// bigNumber adalah konstanta valid meski jauh melebihi int64 max
// Tetapi ketika dipakai sebagai tipe tertentu, ada batasan:
// var n int64 = bigNumber // ✗ overflow — 2^100 tidak muat di int64
const smallEnough = 1 << 62 // masih muat di int64
var n int64 = smallEnough // ✓
Ini juga berarti ekspresi konstanta selalu akurat secara matematika, tidak ada floating-point rounding error selama berada di compile-time:
const exactThird = 1.0 / 3.0 // presisi sangat tinggi di compile-time
// Baru terjadi pembulatan saat diassign ke float64:
var approxThird float64 = exactThird // 0.3333333333333333
iota — Counter Otomatis Go #
iota adalah identifier spesial yang hanya valid di dalam blok const. Nilainya dimulai dari 0 di setiap blok const baru dan bertambah 1 untuk setiap baris konstanta dalam blok tersebut.
const (
Nol = iota // 0
Satu // 1 — iota otomatis bertambah
Dua // 2
Tiga // 3
Empat // 4
)
fmt.Println(Nol, Satu, Dua, Tiga, Empat) // 0 1 2 3 4
iota Reset di Setiap Blok const #
iota selalu mulai dari 0 di setiap blok const yang baru, bukan melanjutkan dari blok sebelumnya:
const (
A = iota // 0
B // 1
C // 2
)
const (
X = iota // 0 — iota RESET, mulai dari 0 lagi
Y // 1
Z // 2
)
Melompati Nilai dengan Blank Identifier #
Jika kamu tidak ingin nilai 0 (yang seringkali membingungkan untuk enum karena zero value), gunakan _ untuk melewatinya:
type LogLevel int
const (
_ LogLevel = iota // 0 — dilewati, tidak ada nama untuk 0
DEBUG // 1
INFO // 2
WARNING // 3
ERROR // 4
FATAL // 5
)
Sekarang var level LogLevel (zero value = 0) tidak akan pernah cocok dengan salah satu level yang valid — ini berguna untuk mendeteksi variabel yang lupa diinisialisasi.
Melompati Banyak Nilai #
const (
_ = iota // 0 — dilewati
_ // 1 — dilewati
_ // 2 — dilewati
ImportantValue // 3
_ // 4 — dilewati
AnotherValue // 5
)
iota dengan Ekspresi — Kekuatan Sebenarnya #
iota baru menunjukkan kekuatan sebenarnya ketika dikombinasikan dengan ekspresi. Ekspresi yang sama diterapkan pada setiap nilai iota secara otomatis:
Bit Flags dengan Bit Shift #
Pola paling klasik: setiap konstanta adalah pangkat dua yang berbeda, cocok untuk permission flags:
type Permission uint8
const (
PermRead Permission = 1 << iota // 1 << 0 = 1 (binary: 00000001)
PermWrite // 1 << 1 = 2 (binary: 00000010)
PermExecute // 1 << 2 = 4 (binary: 00000100)
PermAdmin // 1 << 3 = 8 (binary: 00001000)
PermDelete // 1 << 4 = 16 (binary: 00010000)
)
func hasPermission(userPerm, checkPerm Permission) bool {
return userPerm&checkPerm != 0
}
func main() {
// Kombinasikan permission dengan bitwise OR
editorPerm := PermRead | PermWrite
adminPerm := PermRead | PermWrite | PermExecute | PermAdmin | PermDelete
fmt.Println(hasPermission(editorPerm, PermRead)) // true
fmt.Println(hasPermission(editorPerm, PermAdmin)) // false
fmt.Println(hasPermission(adminPerm, PermDelete)) // true
// Hapus permission dengan bitwise AND NOT
editorPerm &^= PermWrite
fmt.Println(hasPermission(editorPerm, PermWrite)) // false
}
Unit Storage #
type ByteSize float64
const (
_ = iota // abaikan 0
KB ByteSize = 1 << (10 * iota) // 1 << 10 = 1024
MB // 1 << 20 = 1,048,576
GB // 1 << 30 = 1,073,741,824
TB // 1 << 40
PB // 1 << 50
)
func main() {
fmt.Printf("1 KB = %.0f bytes\n", float64(KB))
fmt.Printf("1 MB = %.0f bytes\n", float64(MB))
fmt.Printf("1 GB = %.0f bytes\n", float64(GB))
fileSize := 2.5 * float64(GB)
fmt.Printf("File size: %.2f GB\n", fileSize/float64(GB))
}
Mulai dari Nilai Selain 0 #
type HTTPStatus int
const (
StatusOK HTTPStatus = 200 + iota // 200
StatusCreated // 201
StatusAccepted // 202
StatusNonAuthoritativeInfo // 203
StatusNoContent // 204
)
// Atau kelompok yang terpisah
const (
StatusBadRequest HTTPStatus = 400 + iota // 400
StatusUnauthorized // 401
StatusForbidden HTTPStatus = 403 // 403 — nilai eksplisit, reset iota path
StatusNotFound HTTPStatus = 404
)
Pola Enum Lengkap dengan Method String() #
Go tidak punya tipe enum bawaan, tapi pola idiomatik dengan iota plus method String() memberikan pengalaman yang hampir setara. Method String() membuat nilai enum bisa dicetak dengan nama yang bermakna, bukan hanya angka:
package main
import "fmt"
// Definisi enum
type Weekday int
const (
Sunday Weekday = iota
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
)
// Method String() — otomatis dipanggil oleh fmt saat mencetak
func (d Weekday) String() string {
names := [...]string{
"Sunday",
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
}
if d < Sunday || d > Saturday {
return fmt.Sprintf("Weekday(%d)", int(d))
}
return names[d]
}
// Method tambahan yang membuat enum lebih berguna
func (d Weekday) IsWeekend() bool {
return d == Saturday || d == Sunday
}
func (d Weekday) Next() Weekday {
return (d + 1) % 7 // wrap around — setelah Saturday kembali ke Sunday
}
func main() {
today := Wednesday
fmt.Println(today) // Wednesday — bukan 3
fmt.Println(today.IsWeekend()) // false
fmt.Println(today.Next()) // Thursday
for d := Sunday; d <= Saturday; d++ {
if d.IsWeekend() {
fmt.Printf("%s adalah akhir pekan\n", d)
}
}
}
Ekspresi Campuran dalam Blok const #
Baris dalam blok const tidak harus semua menggunakan iota. Kamu bisa mencampurnya dengan nilai eksplisit:
const (
A = iota // 0
B // 1
C = 100 // 100 — nilai eksplisit, iota tetap bertambah jadi 2
D = iota // 3 — iota melanjutkan dari posisinya, bukan dari C
E // 4
)
fmt.Println(A, B, C, D, E) // 0 1 100 3 4
Pola ini jarang dipakai karena membingungkan. Jika perlu nilai di tengah yang berbeda, lebih baik pisahkan menjadi dua blok const.
Konstanta di Kode Produksi #
Sentinel Values — Nilai yang Punya Makna Spesifik #
package database
import "errors"
// Sentinel errors — error yang bisa diperiksa dengan errors.Is()
var (
ErrNotFound = errors.New("record tidak ditemukan")
ErrDuplicate = errors.New("record sudah ada")
ErrUnauthorized = errors.New("tidak memiliki akses")
)
// Sentinel constants untuk kondisi khusus
const (
NoID = 0
NoLimit = -1 // untuk query tanpa batas
NoOffset = 0
)
func GetUser(id int) (*User, error) {
if id == NoID {
return nil, ErrNotFound
}
// ...
}
Configuration Constants #
package config
const (
// HTTP
DefaultHTTPPort = 8080
DefaultHTTPSPort = 8443
DefaultReadTimeout = 30 // detik
DefaultWriteTimeout = 30 // detik
DefaultIdleTimeout = 120 // detik
// Database
DefaultDBMaxOpenConns = 25
DefaultDBMaxIdleConns = 25
DefaultDBConnMaxLife = 5 // menit
// Cache
DefaultCacheTTL = 300 // detik (5 menit)
DefaultCacheMaxSize = 1000 // entries
// Pagination
DefaultPageSize = 20
MaxPageSize = 100
MinPageSize = 1
)
Hindari Magic Numbers — Gunakan Konstanta Bernama #
// ANTI-PATTERN: magic numbers — apa arti 86400? kenapa 20? kenapa 100?
func processRequest(size int) bool {
if size > 104857600 { // apa ini?
return false
}
time.Sleep(300 * time.Millisecond) // kenapa 300?
return true
}
// BENAR: konstanta bernama membuat kode self-documenting
const (
MaxRequestBodySize = 100 * 1024 * 1024 // 100 MB dalam bytes
RequestProcessDelay = 300 * time.Millisecond
)
func processRequest(size int) bool {
if size > MaxRequestBodySize { // jelas maknanya
return false
}
time.Sleep(RequestProcessDelay) // jelas tujuannya
return true
}
Contoh Program Lengkap #
Program berikut menggabungkan berbagai konsep konstanta dalam skenario sistem manajemen akses:
package main
import "fmt"
// Tipe untuk role dan permission
type Role int
type Permission uint16
// Enum role dengan iota dan String() method
const (
RoleGuest Role = iota
RoleUser
RoleModerator
RoleAdmin
RoleSuperAdmin
)
func (r Role) String() string {
switch r {
case RoleGuest:
return "Guest"
case RoleUser:
return "User"
case RoleModerator:
return "Moderator"
case RoleAdmin:
return "Admin"
case RoleSuperAdmin:
return "SuperAdmin"
default:
return fmt.Sprintf("Role(%d)", int(r))
}
}
// Bit flags untuk permission
const (
PermNone Permission = 0
PermRead Permission = 1 << iota // 1
PermCreate // 2
PermUpdate // 4
PermDelete // 8
PermPublish // 16
PermManageUsers // 32
PermViewAuditLog // 64
// Kombinasi permission yang umum dipakai
PermReadOnly = PermRead
PermEditor = PermRead | PermCreate | PermUpdate
PermPublisher = PermEditor | PermPublish
PermAdminAll = PermPublisher | PermDelete | PermManageUsers | PermViewAuditLog
)
// Batas sistem
const (
MaxUsersPerOrg = 1000
MaxRolesPerUser = 5
TokenExpirySecs = 3600 // 1 jam
PasswordMinLen = 8
PasswordMaxLen = 128
)
type User struct {
Name string
Role Role
Permissions Permission
}
func (u User) Can(perm Permission) bool {
return u.Permissions&perm != 0
}
func (u User) String() string {
return fmt.Sprintf("%s (%s)", u.Name, u.Role)
}
func defaultPermissionsForRole(role Role) Permission {
switch role {
case RoleGuest:
return PermNone
case RoleUser:
return PermReadOnly
case RoleModerator:
return PermEditor
case RoleAdmin:
return PermPublisher | PermManageUsers
case RoleSuperAdmin:
return PermAdminAll
default:
return PermNone
}
}
func main() {
// Buat user dengan permission default berdasarkan role
users := []User{
{Name: "Tamu", Role: RoleGuest, Permissions: defaultPermissionsForRole(RoleGuest)},
{Name: "Budi", Role: RoleUser, Permissions: defaultPermissionsForRole(RoleUser)},
{Name: "Sari", Role: RoleModerator, Permissions: defaultPermissionsForRole(RoleModerator)},
{Name: "Ahmad", Role: RoleAdmin, Permissions: defaultPermissionsForRole(RoleAdmin)},
}
fmt.Println("=== Laporan Akses Pengguna ===")
fmt.Printf("Batas pengguna per organisasi: %d\n\n", MaxUsersPerOrg)
actions := []struct {
name string
perm Permission
}{
{"Baca konten", PermRead},
{"Buat konten", PermCreate},
{"Hapus konten", PermDelete},
{"Kelola pengguna", PermManageUsers},
}
for _, user := range users {
fmt.Printf("Pengguna: %s\n", user)
for _, action := range actions {
status := "✗ tidak boleh"
if user.Can(action.perm) {
status = "✓ boleh"
}
fmt.Printf(" %-25s %s\n", action.name, status)
}
fmt.Println()
}
}
Output program:
=== Laporan Akses Pengguna ===
Batas pengguna per organisasi: 1000
Pengguna: Tamu (Guest)
Baca konten ✗ tidak boleh
Buat konten ✗ tidak boleh
Hapus konten ✗ tidak boleh
Kelola pengguna ✗ tidak boleh
Pengguna: Budi (User)
Baca konten ✓ boleh
Buat konten ✗ tidak boleh
Hapus konten ✗ tidak boleh
Kelola pengguna ✗ tidak boleh
Pengguna: Sari (Moderator)
Baca konten ✓ boleh
Buat konten ✓ boleh
Hapus konten ✗ tidak boleh
Kelola pengguna ✗ tidak boleh
Pengguna: Ahmad (Admin)
Baca konten ✓ boleh
Buat konten ✓ boleh
Hapus konten ✗ tidak boleh
Kelola pengguna ✓ boleh
Ringkasan #
- Konstanta dievaluasi saat kompilasi — nilainya harus diketahui sepenuhnya oleh compiler; tidak bisa memakai hasil fungsi runtime.
- Untyped constants fleksibel — bisa dipakai di berbagai konteks tipe tanpa konversi eksplisit; cocok untuk angka dan string literal.
- Typed constants terikat ke tipe tertentu — berguna untuk enum agar compiler mencegah penggunaan sembarangan.
- Untyped numeric constants punya presisi tak terbatas di compile-time — tidak ada overflow atau rounding error selama masih menjadi konstanta.
iotaadalah counter otomatis dalam blokconst— mulai dari0, bertambah1per baris, dan reset di setiap blokconstbaru._untuk lewati nilai iota yang tidak diinginkan — biasanya untuk menghindari zero value yang ambigu sebagai “belum diinisialisasi.”1 << iotaadalah pola klasik untuk bit flags — setiap konstanta adalah pangkat dua yang unik.- Method
String()membuat tipe enum bisa dicetak dengan nama bermakna, bukan angka.- Gunakan konstanta bernama, bukan magic numbers — kode menjadi self-documenting dan lebih mudah di-maintain.
- Pilih
constatasvaruntuk nilai yang memang tidak berubah — ini adalah komunikasi niat yang jelas kepada pembaca dan mencegah perubahan tidak sengaja.