Skip to main content

Go Operators

Operators are the building blocks of expressions in Go, allowing you to perform calculations, comparisons, logical operations, and more. This comprehensive guide will teach you all of Go's operators, their precedence, associativity, and best practices for writing clear and efficient expressions.

Understanding Operators in Go

What Are Operators?

Operators are special symbols that tell Go how to manipulate values and variables. They enable you to perform various operations such as arithmetic calculations, logical comparisons, and data transformations.

Categories of Go Operators

Go provides several categories of operators:

Arithmetic Operators

  • Addition, subtraction, multiplication, division
  • Modulo (remainder) operation
  • Increment and decrement operations

Comparison Operators

  • Equality and inequality comparisons
  • Relational comparisons (greater than, less than)
  • Type-specific comparisons

Logical Operators

  • Boolean logic operations (AND, OR, NOT)
  • Short-circuit evaluation
  • Conditional logic

Bitwise Operators

  • Bit-level manipulation
  • Bit shifting operations
  • Bitwise logical operations

Assignment Operators

  • Simple and compound assignment
  • Operator shortcuts
  • Multiple assignment

Special Operators

  • Address and pointer operators
  • Channel operations
  • Type assertions

Arithmetic Operators

Basic Arithmetic Operations

package main

import "fmt"

func main() {
// Basic arithmetic with integers
var (
a int = 10
b int = 3
)

// Addition
sum := a + b
fmt.Printf("%d + %d = %d\n", a, b, sum)

// Subtraction
difference := a - b
fmt.Printf("%d - %d = %d\n", a, b, difference)

// Multiplication
product := a * b
fmt.Printf("%d * %d = %d\n", a, b, product)

// Division (integer division for integers)
quotient := a / b
fmt.Printf("%d / %d = %d\n", a, b, quotient)

// Modulo (remainder)
remainder := a % b
fmt.Printf("%d %% %d = %d\n", a, b, remainder)
}

Floating-Point Arithmetic

package main

import "fmt"

func main() {
// Arithmetic with floating-point numbers
var (
x float64 = 10.5
y float64 = 3.2
)

fmt.Printf("x = %.2f, y = %.2f\n", x, y)

// All arithmetic operations work with floats
fmt.Printf("Addition: %.2f + %.2f = %.2f\n", x, y, x + y)
fmt.Printf("Subtraction: %.2f - %.2f = %.2f\n", x, y, x - y)
fmt.Printf("Multiplication: %.2f * %.2f = %.2f\n", x, y, x * y)
fmt.Printf("Division: %.2f / %.2f = %.2f\n", x, y, x / y)

// Note: Modulo operator (%) doesn't work with floats
// fmt.Printf("%.2f %% %.2f = %.2f\n", x, y, x % y) // Compile error
}

Increment and Decrement Operators

package main

import "fmt"

func main() {
// Increment and decrement operators
var counter int = 5

fmt.Printf("Initial counter: %d\n", counter)

// Post-increment (returns value, then increments)
result := counter++
fmt.Printf("After counter++: result=%d, counter=%d\n", result, counter)

// Pre-increment (increments, then returns value)
result = ++counter
fmt.Printf("After ++counter: result=%d, counter=%d\n", result, counter)

// Post-decrement
result = counter--
fmt.Printf("After counter--: result=%d, counter=%d\n", result, counter)

// Pre-decrement
result = --counter
fmt.Printf("After --counter: result=%d, counter=%d\n", result, counter)

// Note: Go doesn't have ++ and -- operators as expressions
// These are statements, not expressions like in C/C++
}

Mixed Type Arithmetic

package main

import "fmt"

func main() {
// Mixed type arithmetic requires explicit conversion
var (
intVal int = 10
floatVal float64 = 3.5
int32Val int32 = 5
int64Val int64 = 15
)

// Can't mix different types directly
// result := intVal + floatVal // Compile error

// Explicit conversion required
result1 := float64(intVal) + floatVal
fmt.Printf("int + float: %.2f\n", result1)

result2 := intVal + int(int32Val)
fmt.Printf("int + int32: %d\n", result2)

result3 := int64(intVal) + int64Val
fmt.Printf("int64 + int64: %d\n", result3)

// Type inference with mixed types
mixed := 10 + 3.5 // Result is float64
fmt.Printf("Mixed literal: %.2f (type inferred)\n", mixed)
}

Comparison Operators

Equality and Inequality

package main

import "fmt"

func main() {
// Equality and inequality operators
var (
a int = 10
b int = 10
c int = 5
)

fmt.Printf("a = %d, b = %d, c = %d\n", a, b, c)

// Equality
fmt.Printf("a == b: %t\n", a == b) // true
fmt.Printf("a == c: %t\n", a == c) // false

// Inequality
fmt.Printf("a != b: %t\n", a != b) // false
fmt.Printf("a != c: %t\n", a != c) // true

// String comparison
str1 := "hello"
str2 := "hello"
str3 := "world"

fmt.Printf("'%s' == '%s': %t\n", str1, str2, str1 == str2) // true
fmt.Printf("'%s' == '%s': %t\n", str1, str3, str1 == str3) // false

// Boolean comparison
var (
flag1 bool = true
flag2 bool = true
flag3 bool = false
)

fmt.Printf("flag1 == flag2: %t\n", flag1 == flag2) // true
fmt.Printf("flag1 == flag3: %t\n", flag1 == flag3) // false
}

Relational Operators

package main

import "fmt"

func main() {
// Relational operators
var (
x int = 10
y int = 5
z int = 15
)

fmt.Printf("x = %d, y = %d, z = %d\n", x, y, z)

// Less than
fmt.Printf("x < y: %t\n", x < y) // false
fmt.Printf("y < z: %t\n", y < z) // true

// Less than or equal
fmt.Printf("x <= y: %t\n", x <= y) // false
fmt.Printf("x <= x: %t\n", x <= x) // true

// Greater than
fmt.Printf("x > y: %t\n", x > y) // true
fmt.Printf("y > z: %t\n", y > z) // false

// Greater than or equal
fmt.Printf("x >= y: %t\n", x >= y) // true
fmt.Printf("x >= x: %t\n", x >= x) // true

// String comparison (lexicographic order)
str1 := "apple"
str2 := "banana"
str3 := "apple"

fmt.Printf("'%s' < '%s': %t\n", str1, str2, str1 < str2) // true
fmt.Printf("'%s' > '%s': %t\n", str1, str2, str1 > str2) // false
fmt.Printf("'%s' <= '%s': %t\n", str1, str3, str1 <= str3) // true
}

Type-Specific Comparisons

package main

import (
"fmt"
"math"
)

func main() {
// Floating-point comparison (be careful with precision)
var (
a float64 = 0.1 + 0.2
b float64 = 0.3
)

fmt.Printf("a = %.20f\n", a)
fmt.Printf("b = %.20f\n", b)
fmt.Printf("a == b: %t\n", a == b) // false due to floating-point precision

// Proper floating-point comparison
const epsilon = 1e-9
isEqual := math.Abs(a - b) < epsilon
fmt.Printf("a ≈ b (within epsilon): %t\n", isEqual) // true

// Complex number comparison (only == and != are defined)
var (
c1 complex128 = 3 + 4i
c2 complex128 = 3 + 4i
c3 complex128 = 4 + 3i
)

fmt.Printf("c1 == c2: %t\n", c1 == c2) // true
fmt.Printf("c1 == c3: %t\n", c1 == c3) // false
// fmt.Printf("c1 < c3: %t\n", c1 < c3) // Compile error: no ordering for complex numbers
}

Logical Operators

Boolean Logic Operations

package main

import "fmt"

func main() {
// Logical operators with boolean values
var (
p bool = true
q bool = false
)

fmt.Printf("p = %t, q = %t\n", p, q)

// Logical AND (&&)
fmt.Printf("p && q: %t\n", p && q) // false
fmt.Printf("p && p: %t\n", p && p) // true
fmt.Printf("q && q: %t\n", q && q) // false

// Logical OR (||)
fmt.Printf("p || q: %t\n", p || q) // true
fmt.Printf("p || p: %t\n", p || p) // true
fmt.Printf("q || q: %t\n", q || q) // false

// Logical NOT (!)
fmt.Printf("!p: %t\n", !p) // false
fmt.Printf("!q: %t\n", !q) // true
fmt.Printf("!!p: %t\n", !!p) // true (double negation)

// Complex logical expressions
complex := (p && !q) || (!p && q)
fmt.Printf("(p && !q) || (!p && q): %t\n", complex) // XOR operation
}

Short-Circuit Evaluation

package main

import "fmt"

func main() {
// Short-circuit evaluation demonstration
fmt.Println("Testing short-circuit evaluation:")

// Function that prints and returns a value
printAndReturn := func(name string, value bool) bool {
fmt.Printf(" Evaluating %s\n", name)
return value
}

fmt.Println("\nTesting && (AND) operator:")
result1 := printAndReturn("A", true) && printAndReturn("B", false)
fmt.Printf("Result: %t\n", result1)

fmt.Println("\nTesting && with short-circuit:")
result2 := printAndReturn("C", false) && printAndReturn("D", true)
fmt.Printf("Result: %t\n", result2) // D is not evaluated

fmt.Println("\nTesting || (OR) operator:")
result3 := printAndReturn("E", false) || printAndReturn("F", true)
fmt.Printf("Result: %t\n", result3)

fmt.Println("\nTesting || with short-circuit:")
result4 := printAndReturn("G", true) || printAndReturn("H", false)
fmt.Printf("Result: %t\n", result4) // H is not evaluated
}

Practical Logical Operations

package main

import "fmt"

func main() {
// Practical examples of logical operations
var (
isLoggedIn bool = true
hasPermission bool = false
isAdmin bool = true
isDebugMode bool = false
)

// Access control logic
canAccess := isLoggedIn && (hasPermission || isAdmin)
fmt.Printf("Can access resource: %t\n", canAccess)

// Debug information logic
shouldLog := isDebugMode || (!isLoggedIn)
fmt.Printf("Should log debug info: %t\n", shouldLog)

// Complex business logic
canEdit := isLoggedIn && hasPermission && !isDebugMode
canDelete := isLoggedIn && isAdmin
canView := isLoggedIn

fmt.Printf("Can edit: %t\n", canEdit)
fmt.Printf("Can delete: %t\n", canDelete)
fmt.Printf("Can view: %t\n", canView)

// Validation logic
isValid := (isLoggedIn || isAdmin) && !(!hasPermission && isDebugMode)
fmt.Printf("Is valid operation: %t\n", isValid)
}

Bitwise Operators

Bitwise Logical Operations

package main

import "fmt"

func main() {
// Bitwise operators work on integer types
var (
a uint8 = 0b10101010 // 170 in decimal
b uint8 = 0b11001100 // 204 in decimal
)

fmt.Printf("a = %08b (%d)\n", a, a)
fmt.Printf("b = %08b (%d)\n", b, b)
fmt.Println()

// Bitwise AND (&)
result := a & b
fmt.Printf("a & b = %08b (%d)\n", result, result)

// Bitwise OR (|)
result = a | b
fmt.Printf("a | b = %08b (%d)\n", result, result)

// Bitwise XOR (^)
result = a ^ b
fmt.Printf("a ^ b = %08b (%d)\n", result, result)

// Bitwise NOT (^)
result = ^a
fmt.Printf("^a = %08b (%d)\n", result, result)

// Note: ^ is both XOR and NOT depending on context
// For NOT, it's ^x, for XOR it's x ^ y
}

Bit Shifting Operations

package main

import "fmt"

func main() {
// Bit shifting operations
var value uint8 = 0b00001010 // 10 in decimal

fmt.Printf("Original value: %08b (%d)\n", value, value)
fmt.Println()

// Left shift (`<<`)
leftShift1 := value << 1
leftShift2 := value << 2
leftShift3 := value << 3

fmt.Printf("Left shift 1: %08b (%d)\n", leftShift1, leftShift1)
fmt.Printf("Left shift 2: %08b (%d)\n", leftShift2, leftShift2)
fmt.Printf("Left shift 3: %08b (%d)\n", leftShift3, leftShift3)
fmt.Println()

// Right shift (`>>`)
rightShift1 := value >> 1
rightShift2 := value >> 2
rightShift3 := value >> 3

fmt.Printf("Right shift 1: %08b (%d)\n", rightShift1, rightShift1)
fmt.Printf("Right shift 2: %08b (%d)\n", rightShift2, rightShift2)
fmt.Printf("Right shift 3: %08b (%d)\n", rightShift3, rightShift3)
fmt.Println()

// Practical example: multiplying and dividing by powers of 2
original := 25
multiplied := original << 2 // Multiply by 4 (2^2)
divided := original >> 1 // Divide by 2 (2^1)

fmt.Printf("Original: %d\n", original)
fmt.Printf("Multiplied by 4: %d\n", multiplied)
fmt.Printf("Divided by 2: %d\n", divided)
}

Practical Bitwise Applications

package main

import "fmt"

func main() {
// Practical bitwise operations

// Setting and clearing bits
var flags uint8 = 0

// Set bit 0 (rightmost bit)
flags |= 1 << 0 // Set bit 0
flags |= 1 << 2 // Set bit 2
flags |= 1 << 4 // Set bit 4

fmt.Printf("Flags after setting bits 0, 2, 4: %08b (%d)\n", flags, flags)

// Check if bit 2 is set
if flags&(1<<2) != 0 {
fmt.Println("Bit 2 is set")
}

// Clear bit 2
flags &^= 1 << 2 // Clear bit 2
fmt.Printf("Flags after clearing bit 2: %08b (%d)\n", flags, flags)

// Toggle bit 3
flags ^= 1 << 3 // Toggle bit 3
fmt.Printf("Flags after toggling bit 3: %08b (%d)\n", flags, flags)

// Count set bits (population count)
count := 0
temp := flags
for temp != 0 {
count += int(temp & 1)
temp >>= 1
}
fmt.Printf("Number of set bits: %d\n", count)
}

Assignment Operators

Simple Assignment

package main

import "fmt"

func main() {
// Simple assignment
var x int = 10
fmt.Printf("Initial x: %d\n", x)

// Assignment operator
x = 20
fmt.Printf("After x = 20: %d\n", x)

// Multiple assignment
var a, b int
a, b = 5, 10
fmt.Printf("a = %d, b = %d\n", a, b)

// Swap values using multiple assignment
a, b = b, a
fmt.Printf("After swap: a = %d, b = %d\n", a, b)

// Assignment with type conversion
var y float64 = 3.14
var z int
z = int(y) // Explicit conversion required
fmt.Printf("y = %.2f, z = %d\n", y, z)
}

Compound Assignment Operators

package main

import "fmt"

func main() {
// Compound assignment operators
var x int = 10

fmt.Printf("Initial x: %d\n", x)

// Addition assignment
x += 5 // Equivalent to x = x + 5
fmt.Printf("After x += 5: %d\n", x)

// Subtraction assignment
x -= 3 // Equivalent to x = x - 3
fmt.Printf("After x -= 3: %d\n", x)

// Multiplication assignment
x *= 2 // Equivalent to x = x * 2
fmt.Printf("After x *= 2: %d\n", x)

// Division assignment
x /= 4 // Equivalent to x = x / 4
fmt.Printf("After x /= 4: %d\n", x)

// Modulo assignment
x %= 3 // Equivalent to x = x % 3
fmt.Printf("After x %%= 3: %d\n", x)

// Bitwise compound assignments
var flags uint8 = 0b00001010

fmt.Printf("Initial flags: %08b\n", flags)

flags |= 0b00000001 // Set bit 0
fmt.Printf("After |= 0b00000001: %08b\n", flags)

flags &^= 0b00001000 // Clear bit 3
fmt.Printf("After &^= 0b00001000: %08b\n", flags)

flags ^= 0b00000100 // Toggle bit 2
fmt.Printf("After ^= 0b00000100: %08b\n", flags)

// Bit shifting compound assignments
flags <<= 1 // Left shift by 1
fmt.Printf("After <<= 1: %08b\n", flags)

flags >>= 2 // Right shift by 2
fmt.Printf("After >>= 2: %08b\n", flags)
}

Special Operators

Address and Pointer Operators

package main

import "fmt"

func main() {
// Address operator (&)
var x int = 42
var y float64 = 3.14

// Get memory addresses
ptrX := &x // Address of x
ptrY := &y // Address of y

fmt.Printf("Value of x: %d\n", x)
fmt.Printf("Address of x: %p\n", ptrX)
fmt.Printf("Value of y: %.2f\n", y)
fmt.Printf("Address of y: %p\n", ptrY)

// Dereference operator (*)
fmt.Printf("Value at ptrX: %d\n", *ptrX)
fmt.Printf("Value at ptrY: %.2f\n", *ptrY)

// Modify value through pointer
*ptrX = 100
*ptrY = 2.71

fmt.Printf("Modified x through pointer: %d\n", x)
fmt.Printf("Modified y through pointer: %.2f\n", y)
}

Channel Operators

package main

import "fmt"

func main() {
// Channel operators (basic introduction)
ch := make(chan int, 1) // Create buffered channel

// Send operator (<-)
ch <- 42 // Send value to channel
fmt.Println("Sent value to channel")

// Receive operator (<-)
value := <-ch // Receive value from channel
fmt.Printf("Received value: %d\n", value)

// Close operator
close(ch)
fmt.Println("Channel closed")
}

Operator Precedence and Associativity

Understanding Precedence

package main

import "fmt"

func main() {
// Operator precedence demonstration
var (
a int = 2
b int = 3
c int = 4
)

fmt.Printf("a = %d, b = %d, c = %d\n", a, b, c)

// Multiplication has higher precedence than addition
result1 := a + b * c // Equivalent to a + (b * c)
result2 := (a + b) * c // Explicit parentheses

fmt.Printf("a + b * c = %d\n", result1) // 2 + (3 * 4) = 14
fmt.Printf("(a + b) * c = %d\n", result2) // (2 + 3) * 4 = 20

// Logical operators precedence
var (
p bool = true
q bool = false
r bool = true
)

// && has higher precedence than ||
logical1 := p || q && r // Equivalent to p || (q && r)
logical2 := (p || q) && r // Explicit parentheses

fmt.Printf("p || q && r = %t\n", logical1) // true || (false && true) = true
fmt.Printf("(p || q) && r = %t\n", logical2) // (true || false) && true = true

// Bitwise operators precedence
var (
x uint8 = 0b10101010
y uint8 = 0b11001100
z uint8 = 0b11110000
)

bitwise1 := x & y | z // Equivalent to (x & y) | z
bitwise2 := x & (y | z) // Explicit parentheses

fmt.Printf("x & y | z = %08b\n", bitwise1)
fmt.Printf("x & (y | z) = %08b\n", bitwise2)
}

Operator Precedence Table

package main

import "fmt"

func main() {
// Go operator precedence (highest to lowest)

// 1. Postfix operators: [] () . ->
// 2. Unary operators: ++ -- + - ! ^ * & <- (channel)
// 3. Binary operators:
// * / % << >> & &^
// + - | ^
// == != < <= > >=
// &&
// ||

// Practical example showing precedence
var (
a int = 10
b int = 3
c int = 2
)

// Complex expression demonstrating precedence
result := a + b * c - a / b % c
// Equivalent to: a + (b * c) - ((a / b) % c)
// Step by step:
// 1. b * c = 3 * 2 = 6
// 2. a / b = 10 / 3 = 3
// 3. (a / b) % c = 3 % 2 = 1
// 4. a + (b * c) = 10 + 6 = 16
// 5. 16 - 1 = 15

fmt.Printf("a + b * c - a / b %% c = %d\n", result)

// Use parentheses for clarity
clearResult := a + (b * c) - ((a / b) % c)
fmt.Printf("Clear version: %d\n", clearResult)
}

Best Practices for Using Operators

Writing Clear Expressions

package main

import "fmt"

func main() {
// Best practices for writing clear expressions

// 1. Use parentheses for clarity, even when not required
var (
a int = 10
b int = 3
c int = 2
)

// Unclear (relies on precedence)
unclear := a + b * c / b - a % c

// Clear (explicit precedence)
clear := a + (b * c / b) - (a % c)

fmt.Printf("Unclear: %d\n", unclear)
fmt.Printf("Clear: %d\n", clear)

// 2. Break complex expressions into multiple statements
// Complex (hard to read)
complex := (a + b) * (c - a) / (b + c) + (a % b)

// Simple (easy to read)
temp1 := a + b
temp2 := c - a
temp3 := b + c
temp4 := a % b
simple := (temp1 * temp2 / temp3) + temp4

fmt.Printf("Complex: %d\n", complex)
fmt.Printf("Simple: %d\n", simple)

// 3. Use meaningful variable names
var (
baseSalary int = 50000
bonus int = 5000
taxRate float64 = 0.25
overtimeHours int = 10
hourlyRate int = 25
)

// Clear calculation
grossPay := baseSalary + bonus + (overtimeHours * hourlyRate)
taxAmount := float64(grossPay) * taxRate
netPay := grossPay - int(taxAmount)

fmt.Printf("Gross Pay: $%d\n", grossPay)
fmt.Printf("Tax Amount: $%.2f\n", taxAmount)
fmt.Printf("Net Pay: $%d\n", netPay)
}

Avoiding Common Pitfalls

package main

import "fmt"

func main() {
// Common pitfalls and how to avoid them

// 1. Floating-point precision issues
var (
price1 float64 = 0.1
price2 float64 = 0.2
total float64 = price1 + price2
)

fmt.Printf("price1: %.20f\n", price1)
fmt.Printf("price2: %.20f\n", price2)
fmt.Printf("total: %.20f\n", total)
fmt.Printf("total == 0.3: %t\n", total == 0.3) // false!

// Solution: Use epsilon for comparison
const epsilon = 1e-9
isEqual := (total - 0.3) < epsilon
fmt.Printf("total ≈ 0.3: %t\n", isEqual)

// 2. Integer division truncation
var (
dividend int = 7
divisor int = 3
)

quotient := dividend / divisor
remainder := dividend % divisor

fmt.Printf("%d / %d = %d (truncated)\n", dividend, divisor, quotient)
fmt.Printf("%d %% %d = %d\n", dividend, divisor, remainder)

// Solution: Use float division when needed
floatQuotient := float64(dividend) / float64(divisor)
fmt.Printf("%d / %d = %.2f (precise)\n", dividend, divisor, floatQuotient)

// 3. Overflow in arithmetic operations
var (
maxInt8 int8 = 127
minInt8 int8 = -128
)

fmt.Printf("maxInt8: %d\n", maxInt8)
fmt.Printf("minInt8: %d\n", minInt8)

// Overflow example (uncomment to see overflow)
// overflow := maxInt8 + 1 // This would cause overflow
// fmt.Printf("maxInt8 + 1: %d\n", overflow)

// Solution: Check for overflow or use larger types
var safeResult int16 = int16(maxInt8) + 1
fmt.Printf("Safe calculation: %d\n", safeResult)
}

What You've Learned

Congratulations! You now have a comprehensive understanding of Go's operators:

Arithmetic Operators

  • Basic arithmetic operations (+, -, *, /, %)
  • Increment and decrement operations
  • Mixed type arithmetic and conversions
  • Floating-point arithmetic considerations

Comparison Operators

  • Equality and inequality comparisons
  • Relational operators for ordering
  • Type-specific comparison behaviors
  • Floating-point comparison best practices

Logical Operators

  • Boolean logic operations (&&, ||, !)
  • Short-circuit evaluation behavior
  • Complex logical expressions
  • Practical boolean logic applications

Bitwise Operators

  • Bit-level manipulation (&, |, ^, ^)
  • Bit shifting operations (<<, >>)
  • Practical bitwise applications
  • Flag manipulation techniques

Assignment Operators

  • Simple and compound assignment
  • Multiple assignment patterns
  • Operator shortcuts and efficiency
  • Type conversion in assignments

Special Operators

  • Address and pointer operators
  • Channel communication operators
  • Operator precedence and associativity
  • Best practices for clear expressions

Next Steps

You now have a solid foundation in Go's operator system. In the next section, we'll explore type conversion in Go, learning how to safely convert between different types and understanding when explicit conversion is required.

Understanding operators is crucial for writing effective expressions and implementing business logic. These operators form the foundation for all the more complex operations and algorithms we'll cover in the coming chapters.


Ready to learn about type conversion? Let's explore how to safely convert between different types in Go in the next section!