unisbadri.com » Python Java Golang Typescript Kotlin Ruby Rust Dart PHP

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 → selalu false, operand kanan tidak dievaluasi
  • true || anything → selalu true, 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 >> 1 menghasilkan -4, bukan 2147483644. 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 ke float64 sebelum pembagian jika butuh desimal.
  • Modulus negatif (%) mengikuti tanda operand kiri — -7 % 3 = -1, bukan 2.
  • 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 (++i tidak valid).
  • <- adalah operator channel — ch <- val untuk send, val := <-ch untuk receive.
  • Tidak ada ternary ? : di Go — gunakan if-else yang lebih eksplisit.
  • Tidak ada operator ** untuk pangkat — gunakan math.Pow() atau bit shift untuk pangkat 2.
  • Gunakan parentheses ketika ekspresi melibatkan banyak operator — lebih jelas dari mengandalkan hafalan presedan.

← Sebelumnya: Tipe Data   Berikutnya: Seleksi Kondisi →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact