Operator #
Operator di Go terlihat familiar bagi siapa pun yang pernah menulis kode dalam bahasa C, Java, atau Python — tapi ada beberapa keputusan desain yang membuat Go berbeda. ++ dan -- adalah statement, bukan ekspresi, sehingga kamu tidak bisa menulis x = y++. Tidak ada ternary operator ? :. Tidak ada ** untuk pangkat. Ada operator &^ (AND NOT) yang tidak ada di kebanyakan bahasa lain. Dan operator <- khusus untuk channel yang menjadi inti concurrency Go. Memahami operator Go bukan sekadar menghafal simbol — ini tentang memahami mengapa desainnya seperti ini.
Operator Aritmatika #
Operator aritmatika bekerja pada tipe numerik dan mengikuti aturan matematika standar, dengan beberapa catatan penting.
a := 17
b := 5
fmt.Println(a + b) // 22 — penjumlahan
fmt.Println(a - b) // 12 — pengurangan
fmt.Println(a * b) // 85 — perkalian
fmt.Println(a / b) // 3 — pembagian INTEGER (bukan 3.4!)
fmt.Println(a % b) // 2 — modulus (sisa pembagian)
Pembagian Integer — Gotcha yang Sering Mengejutkan #
Ketika dua operand adalah integer, / melakukan integer division — hasilnya selalu dibulatkan ke nol, bukan ke bawah:
fmt.Println(7 / 2) // 3 (bukan 3.5)
fmt.Println(-7 / 2) // -3 (bukan -4, dibulatkan ke Nول, bukan ke bawah)
fmt.Println(7 / -2) // -3
// ANTI-PATTERN: kalkulasi yang butuh desimal tapi lupa konversi
func hitungRataRata(total, count int) float64 {
return float64(total / count) // ✗ pembagian integer dulu, baru konversi
// total=7, count=2 → 7/2=3 → float64(3) = 3.0 (bukan 3.5!)
}
// BENAR: konversi ke float64 SEBELUM pembagian
func hitungRataRata(total, count int) float64 {
return float64(total) / float64(count) // ✓ 7.0/2.0 = 3.5
}
Untuk pembagian float, salah satu atau kedua operand harus float:
fmt.Println(7.0 / 2) // 3.5
fmt.Println(7 / 2.0) // 3.5
fmt.Println(float64(7) / float64(2)) // 3.5
Modulus dan Nilai Negatif #
Operator % di Go mengikuti aturan: tanda hasil selalu sama dengan tanda operand kiri (dividend):
fmt.Println( 7 % 3) // 1
fmt.Println(-7 % 3) // -1 (tanda negatif mengikuti -7)
fmt.Println( 7 % -3) // 1 (tanda positif mengikuti 7)
fmt.Println(-7 % -3) // -1
// Cek genap/ganjil yang aman (bekerja untuk bilangan negatif)
func isEven(n int) bool {
return n%2 == 0 // aman: -4%2 = 0 (genap), -3%2 = -1 (ganjil, bukan nol)
}
// Jika butuh modulus selalu positif (seperti Python):
func positiveMod(a, b int) int {
return ((a % b) + b) % b
}
fmt.Println(positiveMod(-7, 3)) // 2 (bukan -1)
Operator + pada String
#
Operator + juga bekerja pada string untuk konkatenasi:
firstName := "Budi"
lastName := "Santoso"
fullName := firstName + " " + lastName
fmt.Println(fullName) // "Budi Santoso"
// Tapi ingat: setiap + membuat string baru di memori
// Untuk banyak konkatenasi, gunakan strings.Builder (lihat artikel Tipe Data)
Operator Perbandingan #
Operator perbandingan selalu menghasilkan bool. Semua operator perbandingan bekerja sesuai ekspektasi untuk tipe dasar, tapi ada aturan comparability yang perlu dipahami untuk tipe komposit.
a, b := 10, 20
fmt.Println(a == b) // false — sama dengan
fmt.Println(a != b) // true — tidak sama dengan
fmt.Println(a < b) // true — kurang dari
fmt.Println(a > b) // false — lebih dari
fmt.Println(a <= b) // true — kurang dari atau sama dengan
fmt.Println(a >= b) // false — lebih dari atau sama dengan
Comparability — Tidak Semua Tipe Bisa Dibandingkan #
// Tipe yang BISA dibandingkan dengan == dan !=:
// int, float, bool, string, pointer, channel, interface, struct (jika semua field-nya comparable)
// Struct bisa dibandingkan jika semua field-nya comparable
type Point struct {
X, Y int
}
p1 := Point{1, 2}
p2 := Point{1, 2}
fmt.Println(p1 == p2) // true — semua field sama
// Tipe yang TIDAK BISA dibandingkan dengan == (compile error):
s1 := []int{1, 2, 3}
s2 := []int{1, 2, 3}
// fmt.Println(s1 == s2) // ← compile error: slice can only be compared to nil
fmt.Println(s1 == nil) // ✓ slice hanya bisa dibanding dengan nil
m := map[string]int{"a": 1}
// fmt.Println(m == map[string]int{"a": 1}) // ← compile error
fmt.Println(m == nil) // ✓ map hanya bisa dibanding dengan nil
// Untuk membandingkan slice/map, gunakan reflect.DeepEqual atau loop manual
import "reflect"
fmt.Println(reflect.DeepEqual(s1, s2)) // true
Perbandingan Pointer #
x := 42
p1 := &x
p2 := &x
p3 := new(int)
*p3 = 42
fmt.Println(p1 == p2) // true — keduanya menunjuk ke variabel x yang sama
fmt.Println(p1 == p3) // false — menunjuk ke memori yang berbeda, meski nilainya sama
fmt.Println(*p1 == *p3) // true — nilai yang ditunjuk sama
Perbandingan String #
String dibandingkan secara lexicographic (leksikografi) — berdasarkan urutan byte:
fmt.Println("apple" == "apple") // true
fmt.Println("apple" < "banana") // true — 'a' < 'b'
fmt.Println("apple" < "Apple") // false — 'a' (97) > 'A' (65) dalam ASCII
fmt.Println("abc" < "abd") // true — sama sampai 'c' vs 'd'
// Perbandingan case-insensitive:
import "strings"
fmt.Println(strings.EqualFold("Go", "go")) // true — case-insensitive
fmt.Println(strings.EqualFold("GO", "go")) // true
Operator Logika #
Operator logika bekerja pada nilai bool dan selalu menghasilkan bool.
x, y := true, false
fmt.Println(x && y) // false — AND: keduanya harus true
fmt.Println(x || y) // true — OR: salah satu cukup true
fmt.Println(!x) // false — NOT: negasi
Short-Circuit Evaluation — Lebih dari Sekadar Optimasi #
Short-circuit adalah sifat fundamental && dan ||: Go tidak mengevaluasi operand kanan jika hasil sudah bisa ditentukan dari operand kiri:
false && anything→ selalufalse, operand kanan tidak dievaluasitrue || anything→ selalutrue, operand kanan tidak dievaluasi
Ini bukan hanya masalah performa — ini adalah pola keamanan kritis:
// Pola 1: nil guard — cek nil sebelum dereference
var user *User = nil
if user != nil && user.IsAdmin() {
// user.IsAdmin() tidak akan dipanggil jika user == nil
// tanpa short-circuit, ini akan panic
fmt.Println("admin")
}
// Pola 2: validasi berantai — hentikan awal jika ada yang gagal
func isValidRequest(r *Request) bool {
return r != nil &&
len(r.Body) > 0 &&
len(r.Body) <= MaxBodySize &&
isValidToken(r.Token) // hanya dicek jika semua kondisi sebelumnya true
}
// Pola 3: lazy initialization dengan ||
func getConfig() *Config {
return cachedConfig || loadFromDisk()
// loadFromDisk() hanya dipanggil jika cachedConfig adalah falsy
}
// Pola 4: kondisi mahal di kanan
if isSimpleCheck(x) && isExpensiveCheck(x) {
// isExpensiveCheck hanya dipanggil jika isSimpleCheck true
}
Letakkan kondisi yang paling mungkin false di sisi kiri&&, dan kondisi yang paling mungkin true di sisi kiri||. Ini memaksimalkan manfaat short-circuit — operasi mahal di kanan hanya dijalankan ketika benar-benar diperlukan.
Operator Bitwise #
Operator bitwise bekerja pada representasi biner dari integer. Digunakan untuk manipulasi bit-level, permission flags, masking, dan optimasi tertentu.
a := 0b1010 // binary: 1010, decimal: 10
b := 0b1100 // binary: 1100, decimal: 12
AND Bitwise (&) — Bit 1 Hanya Jika Keduanya 1
#
fmt.Println(a & b)
// 1010
// & 1100
// ------
// 1000 = 8
// Kegunaan: mengekstrak atau memeriksa bit tertentu
const FlagActive = 0b0001
const FlagAdmin = 0b0010
const FlagVIP = 0b0100
userFlags := 0b0011 // Active + Admin
fmt.Println(userFlags & FlagActive != 0) // true — user aktif
fmt.Println(userFlags & FlagAdmin != 0) // true — user admin
fmt.Println(userFlags & FlagVIP != 0) // false — user bukan VIP
OR Bitwise (|) — Bit 1 Jika Salah Satu atau Keduanya 1
#
fmt.Println(a | b)
// 1010
// | 1100
// ------
// 1110 = 14
// Kegunaan: menambahkan flag
userFlags |= FlagVIP // tambahkan flag VIP
fmt.Println(userFlags & FlagVIP != 0) // true — sekarang VIP
XOR Bitwise (^) — Bit 1 Jika Keduanya Berbeda
#
fmt.Println(a ^ b)
// 1010
// ^ 1100
// ------
// 0110 = 6
// Kegunaan: toggle bit (mengubah 0 jadi 1 dan sebaliknya)
userFlags ^= FlagAdmin // toggle admin: jika ada dihapus, jika tidak ada ditambah
AND NOT Bitwise (&^) — Unik di Go
#
&^ adalah operator yang jarang ada di bahasa lain. Ia membersihkan bit: bit di hasil adalah 1 hanya jika bit pertama 1 dan bit kedua 0:
fmt.Println(a &^ b)
// 1010
// &^1100
// ------
// 0010 = 2 (bit yang ada di a tapi TIDAK ada di b)
// Kegunaan: menghapus flag spesifik
userFlags &^= FlagAdmin // hapus flag Admin, tanpa mengubah flag lain
fmt.Println(userFlags & FlagAdmin != 0) // false — admin sudah dihapus
// Di bahasa lain, ini dilakukan dengan: userFlags &= ~FlagAdmin
// Di Go, &^ lebih bersih dan tidak butuh operator NOT (~)
Left Shift (<<) dan Right Shift (>>)
#
x := 1
fmt.Println(x << 1) // 2 — kalikan dengan 2
fmt.Println(x << 2) // 4 — kalikan dengan 4
fmt.Println(x << 3) // 8 — kalikan dengan 8
fmt.Println(x << 10) // 1024 — 2^10
fmt.Println(16 >> 1) // 8 — bagi dengan 2
fmt.Println(16 >> 2) // 4 — bagi dengan 4
// Kegunaan klasik: mendefinisikan konstanta ukuran
const (
KB = 1 << 10 // 1024
MB = 1 << 20 // 1,048,576
GB = 1 << 30 // 1,073,741,824
)
Right shift pada integer signed menggunakan arithmetic shift di Go — bit tanda (sign bit) dipropagasi. Ini berarti
-8 >> 1menghasilkan-4, bukan2147483644. Jika kamu butuh logical shift (mengisi dengan 0), gunakan tipe unsigned.fmt.Println(-8 >> 1) // -4 (signed: arithmetic shift) fmt.Println(uint(-8) >> 1) // 9223372036854775804 (unsigned: logical shift)
Operator Penugasan #
Go menyediakan compound assignment yang menggabungkan operasi dengan assignment:
x := 10
x += 5 // x = x + 5 → 15
x -= 3 // x = x - 3 → 12
x *= 2 // x = x * 2 → 24
x /= 4 // x = x / 4 → 6
x %= 4 // x = x % 4 → 2
// Bitwise compound assignment
flags := 0b1010
flags &= 0b1100 // AND
flags |= 0b0001 // OR
flags ^= 0b0011 // XOR
flags &^= 0b0010 // AND NOT
flags <<= 1 // left shift
flags >>= 1 // right shift
++ dan -- — Statement, Bukan Ekspresi
#
Di Go, ++ dan -- adalah statement, bukan ekspresi. Artinya, mereka tidak menghasilkan nilai dan tidak bisa digunakan dalam ekspresi yang lebih besar:
i := 5
i++ // ✓ valid — statement
i-- // ✓ valid — statement
// ANTI-PATTERN: semua ini adalah compile error di Go
// x = i++ // ✗ tidak valid — ++ bukan ekspresi
// fmt.Println(i++) // ✗ tidak valid
// j := i++ // ✗ tidak valid
// ++i // ✗ tidak valid — Go tidak punya prefix ++
// Hanya ada satu bentuk: postfix, sebagai statement mandiri
for i := 0; i < 5; i++ { // ✓ dalam for statement
fmt.Println(i)
}
Mengapa? Di C dan Java, i++ sebagai ekspresi adalah sumber kebingungan klasik: x = i++ berbeda dari x = ++i. Go menghilangkan ambiguitas ini sepenuhnya dengan menjadikan ++ hanya bisa digunakan sebagai statement mandiri.
Operator Address dan Dereference #
Sudah dibahas di artikel Tipe Data, tapi penting diulangi dalam konteks operator:
x := 42
p := &x // & = "address of" — mengambil alamat memori x, menghasilkan *int
*p = 100 // * = "dereference" — mengakses nilai di alamat yang disimpan p
fmt.Println(x) // 100 — x berubah melalui pointer
fmt.Println(*p) // 100 — sama dengan nilai x
fmt.Println(p) // 0xc000... — alamat memori
// * juga digunakan dalam DEKLARASI TIPE untuk menyatakan "pointer ke T"
var q *int = &x // q adalah pointer ke int
Operator Channel (<-)
#
Operator <- adalah operator khusus untuk komunikasi via channel — inti dari concurrency Go:
ch := make(chan int, 1) // buffered channel, kapasitas 1
// Kirim nilai ke channel (send)
ch <- 42
// Terima nilai dari channel (receive)
nilai := <-ch
fmt.Println(nilai) // 42
// Receive dengan cek apakah channel masih terbuka
nilai, ok := <-ch
if !ok {
fmt.Println("channel sudah ditutup")
}
// Receive tanpa menyimpan nilai (hanya untuk sinkronisasi)
<-done // tunggu sampai channel done menerima nilai
Presedan Operator #
Dalam satu ekspresi dengan beberapa operator, Go mengikuti aturan presedan (precedence) — operator dengan presedan lebih tinggi dievaluasi lebih dulu:
Presedan (dari tertinggi ke terendah):
5: * / % << >> & &^
4: + - | ^
3: == != < <= > >=
2: &&
1: ||
// Contoh presedan
fmt.Println(2 + 3*4) // 14, bukan 20 (*lebih tinggi dari +)
fmt.Println(5 > 3 && 2 < 4) // true (&& lebih rendah dari >)
fmt.Println(true || false && false) // true (&& lebih tinggi dari ||)
// true || (false && false)
// true || false
// true
// Jika tidak yakin, selalu gunakan parentheses
// Lebih jelas dan tidak bergantung pada hafalan presedan:
fmt.Println((2 + 3) * 4) // 20 — eksplisit
fmt.Println(true || (false && false)) // true — jelas
Gunakan parentheses ketika ragu. Kode yang jelas lebih baik dari kode yang “pintar” tapi bergantung pada urutan presedan yang tidak semua orang hafal. Compiler tidak peduli — tapi pembaca manusia sangat terbantu.
Yang Sengaja Tidak Ada di Go #
Go dengan sengaja menghilangkan beberapa operator yang ada di bahasa lain:
// 1. Tidak ada ternary operator (?:)
// Di Java/C: int max = a > b ? a : b;
// ANTI-PATTERN: tidak bisa ditulis seperti ini di Go
// max := a > b ? a : b // ← compile error
// BENAR: gunakan if-else biasa
max := a
if b > a {
max = b
}
// Atau dalam satu ekspresi dengan fungsi:
func ternary(cond bool, a, b int) int {
if cond {
return a
}
return b
}
// 2. Tidak ada operator pangkat (**)
// Di Python: x = 2 ** 10
// BENAR: gunakan math.Pow
import "math"
x := math.Pow(2, 10) // 1024.0 (float64)
// Untuk integer pangkat kecil, lebih efisien hardcode atau shift:
x2 := 1 << 10 // 1024 (int) — hanya untuk pangkat 2
// 3. Tidak ada prefix ++ dan --
// Di C: ++i dan --i
// Go hanya punya postfix, dan hanya sebagai statement:
i++ // ✓
// ++i // ✗ compile error
// 4. Tidak ada operator ~ (bitwise NOT)
// Di C: ~flags
// BENAR: gunakan XOR dengan semua bit 1
flags := 0b1010
notFlags := flags ^ -1 // XOR dengan -1 (semua bit 1) = NOT
// Atau untuk ukuran tertentu:
notFlags8 := ^uint8(flags) // ^ sebagai unary operator = bitwise NOT
Pola Idiomatik dengan Operator #
Swap Tanpa Variabel Temporary #
a, b := 10, 20
a, b = b, a // swap elegan — tidak butuh temp variable
fmt.Println(a, b) // 20 10
Cek Bit dengan Masking #
// Sistem permission berbasis bit
type Perm uint8
const (
Read Perm = 1 << iota // 001
Write // 010
Execute // 100
)
func checkPerm(userPerm, needed Perm) bool {
return userPerm&needed == needed // semua bit di needed harus ada di userPerm
}
userPerm := Read | Write // 011
fmt.Println(checkPerm(userPerm, Read)) // true
fmt.Println(checkPerm(userPerm, Execute)) // false
fmt.Println(checkPerm(userPerm, Read|Write)) // true — cek kombinasi
fmt.Println(checkPerm(userPerm, Read|Execute)) // false — tidak punya Execute
Alignment dan Padding dengan Bitwise #
// Bulatkan n ke kelipatan power-of-2 terdekat ke atas
func alignTo(n, alignment int) int {
return (n + alignment - 1) &^ (alignment - 1)
}
fmt.Println(alignTo(13, 8)) // 16 — bulatkan ke kelipatan 8
fmt.Println(alignTo(16, 8)) // 16 — sudah kelipatan 8
fmt.Println(alignTo(17, 8)) // 24
Contoh Program Lengkap #
Program berikut mensimulasikan sistem kontrol akses berbasis bit flags menggunakan berbagai operator:
package main
import (
"fmt"
"strings"
)
type Permission uint8
const (
PermRead Permission = 1 << iota // 00000001
PermWrite // 00000010
PermDelete // 00000100
PermAdmin // 00001000
PermAudit // 00010000
)
var permNames = map[Permission]string{
PermRead: "Read",
PermWrite: "Write",
PermDelete: "Delete",
PermAdmin: "Admin",
PermAudit: "Audit",
}
func (p Permission) String() string {
if p == 0 {
return "None"
}
var parts []string
for perm, name := range permNames {
if p&perm != 0 {
parts = append(parts, name)
}
}
return strings.Join(parts, "|")
}
func (p Permission) Has(perm Permission) bool {
return p&perm == perm
}
func (p *Permission) Grant(perm Permission) {
*p |= perm
}
func (p *Permission) Revoke(perm Permission) {
*p &^= perm
}
func (p *Permission) Toggle(perm Permission) {
*p ^= perm
}
type User struct {
Name string
Perm Permission
}
func main() {
users := []User{
{Name: "Alice", Perm: PermRead | PermWrite},
{Name: "Bob", Perm: PermRead},
{Name: "Carlos", Perm: PermRead | PermWrite | PermDelete | PermAdmin},
}
fmt.Println("=== Status Permission Awal ===")
for _, u := range users {
fmt.Printf("%-8s → %s\n", u.Name, u.Perm)
}
fmt.Println("\n=== Operasi Permission ===")
// Operator |= untuk grant
users[1].Perm.Grant(PermWrite)
fmt.Printf("Bob grant Write → %s\n", users[1].Perm)
// Operator &^= untuk revoke
users[2].Perm.Revoke(PermAdmin)
fmt.Printf("Carlos revoke Admin → %s\n", users[2].Perm)
// Operator ^= untuk toggle
users[0].Perm.Toggle(PermDelete)
fmt.Printf("Alice toggle Delete → %s\n", users[0].Perm)
users[0].Perm.Toggle(PermDelete) // toggle lagi = kembali ke semula
fmt.Printf("Alice toggle Delete → %s\n", users[0].Perm)
fmt.Println("\n=== Cek Akses ===")
actions := []struct {
name string
perm Permission
}{
{"Baca file", PermRead},
{"Edit file", PermWrite},
{"Hapus file", PermDelete},
{"Kelola user", PermAdmin},
{"Lihat audit", PermAudit},
}
for _, u := range users {
fmt.Printf("\n%s:\n", u.Name)
for _, action := range actions {
// Operator & dan == untuk pengecekan
allowed := u.Perm.Has(action.perm)
status := "✗"
if allowed {
status = "✓"
}
fmt.Printf(" %s %-15s\n", status, action.name)
}
}
// Demonstrasi operator aritmatika dan perbandingan
fmt.Println("\n=== Statistik ===")
totalUsers := len(users)
adminCount := 0
for _, u := range users {
if u.Perm.Has(PermAdmin) {
adminCount++
}
}
// Kalkulasi persentase — ingat: konversi ke float64 SEBELUM pembagian
adminPct := float64(adminCount) / float64(totalUsers) * 100
fmt.Printf("Total user: %d\n", totalUsers)
fmt.Printf("Admin: %d (%.1f%%)\n", adminCount, adminPct)
fmt.Printf("Non-admin: %d\n", totalUsers-adminCount)
}
Ringkasan #
- Pembagian integer (
/) selalu membulatkan ke nol — konversi kefloat64sebelum pembagian jika butuh desimal.- Modulus negatif (
%) mengikuti tanda operand kiri —-7 % 3 = -1, bukan2.- Short-circuit
&&dan||tidak mengevaluasi operand kanan jika sudah bisa ditentukan — gunakan ini untuk nil guard dan validasi berantai.&^(AND NOT) adalah operator unik Go untuk menghapus bit spesifik tanpa mengubah bit lain.<<(left shift) sama dengan perkalian pangkat 2;>>sama dengan pembagian pangkat 2.++dan--adalah statement, bukan ekspresi — tidak bisa digunakan dalam assignment atau sebagai prefix (++itidak valid).<-adalah operator channel —ch <- valuntuk send,val := <-chuntuk receive.- Tidak ada ternary
? :di Go — gunakanif-elseyang lebih eksplisit.- Tidak ada operator
**untuk pangkat — gunakanmath.Pow()atau bit shift untuk pangkat 2.- Gunakan parentheses ketika ekspresi melibatkan banyak operator — lebih jelas dari mengandalkan hafalan presedan.