Mutability and Data Sharing
Overview
Understanding when data is copied vs shared is crucial for avoiding bugs and writing efficient Go code.
Value vs Reference Semantics
Value Types (Copied)
// Integers, floats, bools, strings, arrays, structs
a := 1
b := a
b = 2
fmt.Println(a) // 1 (unchanged)
arr := [3]int{1, 2, 3}
arr2 := arr
arr2[0] = 100
fmt.Println(arr[0]) // 1 (unchanged)Controlling Mutability
Immutable by Design
// Return new instead of modifying
func addItem(items []string, item string) []string {
return append(items, item) // May return new slice
}Defensive Copy
func process(s []int) {
local := make([]int, len(s))
copy(local, s) // Safe to modify local
}Deep Copy
type User struct {
Name string
Friends []string
}
func (u User) Clone() User {
friends := make([]string, len(u.Friends))
copy(friends, u.Friends)
return User{Name: u.Name, Friends: friends}
}Function Parameters
// Value: caller's data safe
func process(u User) {
u.Name = "modified" // Doesn't affect caller
}
// Pointer: can modify caller's data
func process(u *User) {
u.Name = "modified" // Affects caller
}Struct Field Mutability
type Counter struct {
count int
}
// Value receiver: cannot modify
func (c Counter) Increment() {
c.count++ // Modifies copy, original unchanged
}
// Pointer receiver: modifies original
func (c *Counter) Increment() {
c.count++ // Original modified
}Slice Gotchas
// Append may or may not share backing array
s := []int{1, 2, 3}
s2 := s[:2] // Shares backing array
s2 = append(s2, 4) // Might overwrite s[2]!
// Safer: force new allocation
s2 := append([]int(nil), s[:2]...)Summary
| Type | Behavior | Copy When… |
|---|---|---|
| int, bool, string | Value | Always copied |
| struct | Value | Always copied |
| array | Value | Always copied |
| slice | Reference | Use copy() |
| map | Reference | Rebuild manually |
| pointer | Reference | - |