Operators in Go seem trivial, but their interactions with types can silently trigger allocations. Let’s dissect arithmetic, comparison, and bitwise operators alongside type conversions to expose their memory impact.
The Code (Now with Explicit Operators)
package main
func main() {
// CASE 1: Arithmetic operators with mixed types
a := 100 // int (stack)
b := int32(200) // int32 (stack)
sum := a + int(b) // int(b) forces conversion, sum on stack
// CASE 2: String concatenation (operator +)
s1 := "Hello" // data segment
s2 := "Go" // data segment
result := s1 + " " + s2 // New string allocation in data
// CASE 3: Interface comparison (operator ==)
var v1 interface{} = sum // Heap escape
var v2 interface{} = 300 // Heap escape
if v1 == v2 { // Compares type and value
// ...
}
}
Memory Segmentation Visualized
+-------------------+ Lowest Address
| Code |
|-------------------|
| main() | // Arithmetic, comparison logic
| string concat | // Runtime functions for +
+-------------------+
| Data |
|-------------------|
| "Hello" |
| "Go" |
| "Hello Go" | // result's new string
+-------------------+
| Stack |
|-------------------|
| a (int = 100) |
| b (int32 = 200) |
| sum (int = 300) |
| s1, s2 (strings) | // Pointers to data
| result (string) → | // Points to new data segment
+-------------------+
| Heap |
|-------------------|
| v1 (eface) | // Boxed int (300)
| v2 (eface) | // Boxed int (300)
+-------------------+ Highest Address
Operator Breakdown
1. Arithmetic Operators (+
, -
, *
, /
)
-
Mixed types:
a + int(b)
forces anint32
→int
conversion.- Cost: Stack-only operation (no allocation).
-
Division:
/
on integers is stack-resident but may panic (e.g., division by zero).
2. String Concatenation (+
)
-
Allocation:
s1 + " " + s2
creates a new string in the data segment.- Hidden cost: O(n) time and space (avoids mutation).
- Verify:
go build -gcflags="-m" main.go
# ./main.go:9:18: " " escapes to heap # Part of new string allocation
3. Comparison Operator (==
)
-
Interfaces:
v1 == v2
compares both type and value.-
Heap cost:
v1
andv2
box integers intointerface{}
, forcing heap allocations. - Alternative: Compare primitives directly to avoid boxing:
if sum == 300 { /* No allocation */ }
-
Heap cost:
Type Conversion Nuances
- Numeric Conversions
x := 3.14
y := int(x) // Stack: truncates float to int (no allocation)
- String ↔ Slice
s := "Go"
b := []byte(s) // Heap: allocates new []byte backing array
-
Data segment:
s
remains immutable;b
is a heap-allocated copy.
Why This Matters
-
String
+
in loops: Can bloat the data segment (e.g., building HTML/CSS).-
Fix: Use
strings.Builder
orbytes.Buffer
.
-
Fix: Use
- Interface comparisons: Useful but heap-heavy. Avoid in hot paths.
-
Mixed-type arithmetic: May cause silent overflows (e.g.,
int32(bigInt)
).
Optimization Tips
-
Prefer
switch
type assertions overinterface{}
comparisons:
switch v := v1.(type) {
case int:
if v == 300 { ... } // No allocation
}
-
Avoid
+
for large strings:
var builder strings.Builder
builder.WriteString("Hello") // Heap buffer, but amortized growth
builder.WriteString("Go")
result := builder.String() // Single data segment allocation