Go Specification: Pointers with Maps & Slices¶
Source: https://go.dev/ref/spec#Slice_types, https://go.dev/ref/spec#Map_types
1. Spec Reference¶
| Field | Value |
|---|---|
| Slice types | https://go.dev/ref/spec#Slice_types |
| Map types | https://go.dev/ref/spec#Map_types |
| Address operators | https://go.dev/ref/spec#Address_operators |
| Go Version | Go 1.0+ |
2. Definition¶
This topic covers the interaction between pointers and Go's two main reference types — slices and maps. Key facts: - Slices are passed by value (header copied), but share their backing array. - Maps are passed by value (handle copied), but share the underlying hash table. - Slice elements ARE addressable: &s[i] is valid. - Map values are NOT addressable: &m[k] is a compile error. - Storing pointers in slices or maps is common; but be aware of GC and aliasing implications.
3. Core Rules & Constraints¶
3.1 Slice Header Layout¶
24 bytes on 64-bit. The array pointer points to the backing array.
3.2 Map Header¶
A map "value" is a pointer to an hmap struct. 8 bytes.
3.3 Slice Elements Are Addressable¶
s := []int{1, 2, 3}
p := &s[0] // *int, points to s's backing array
*p = 99
fmt.Println(s) // [99 2 3]
3.4 Map Values Are NOT Addressable¶
The map runtime may rehash and move values; stable addresses would be unsafe.
3.5 Method Call on Map Value Field¶
type Counter struct{ N int }
func (c *Counter) Inc() { c.N++ }
m := map[string]Counter{"a": {N: 1}}
// m["a"].Inc() // compile error
Workaround: store pointers, or extract-mutate-restore.
3.6 Slice of Pointers vs Pointer to Slice¶
ptrs := []*Item{} // slice of pointers; common
sliceP := &items // pointer to slice; rare (use when reassigning the slice variable)
3.7 Map Value as Pointer¶
m := map[string]*Counter{}
m["a"] = &Counter{N: 1}
m["a"].Inc() // OK: pointer is addressable through dereference
This is the standard pattern for mutable map values.
3.8 append and Slice Reallocation¶
s := make([]int, 3, 5)
s = append(s, 99) // fits in cap; same array
s = append(s, ...big...) // exceeds cap; new array allocated; old refs stale
If you stored pointers to slice elements, after a reallocation those pointers become stale (point to old array).
4. Type Rules¶
4.1 Slice Pointer Is Different From Slice¶
[]T: slice header (24 B).*[]T: pointer to slice header (8 B). Use only when you need to reassign the slice from a function.
4.2 Map Pointer Is Almost Never Useful¶
A map value is already a pointer to hmap. *map[K]V adds an extra layer of indirection rarely needed.
5. Behavioral Specification¶
5.1 Slice Element Pointers Stable Until Realloc¶
s := make([]int, 3, 10)
p := &s[0]
s = append(s, 99) // cap 10; no realloc
*p = 999
fmt.Println(s) // [999 0 0 99]
But:
s := make([]int, 3, 3)
p := &s[0]
s = append(s, 99) // realloc; new array
*p = 999 // modifies OLD array; not visible in s
5.2 Map Mutation Visible Through All References¶
5.3 Slice Subset Shares Backing¶
6. Defined vs Undefined Behavior¶
| Situation | Behavior |
|---|---|
&s[i] for slice s | Defined — pointer to element |
&m[k] for map m | Compile error |
| Pointer to slice element after append realloc | Stale (points to old array) |
*[]T parameter | Defined — pointer to slice header |
*map[K]V parameter | Defined but rarely useful |
| Method call on map value field | Compile error (not addressable) |
Iterating with for i := range s then &s[i] | Defined — pointer to current element |
| Modifying map during iteration | Defined — newly added entries may or may not appear |
| Modifying slice during iteration | Defined — undefined behavior may result depending on operations |
7. Edge Cases from Spec¶
7.1 Pointer to Index¶
7.2 Map of Pointers, Mutation¶
7.3 Slice Header Pointer¶
7.4 Stale Pointer After Append¶
s := make([]int, 3, 3)
p := &s[0]
s = append(s, 99) // reallocates
fmt.Println(*p, s) // 1 [1 0 0 99] — *p is stale
8. Version History¶
| Go Version | Change |
|---|---|
| Go 1.0 | Slice/map semantics established |
| Go 1.21 | slices and maps packages with helpers |
9. Implementation-Specific Behavior¶
9.1 Slice Backing Array Allocation¶
- Stack if it doesn't escape.
- Heap if it does.
9.2 Map Hash Table Allocation¶
Always heap (the runtime needs a stable home).
9.3 Map Value Storage¶
Maps with pointer values: each value is a pointer (8 B). Map with value-typed values: stored inline in hash buckets.
For large value types in maps, prefer pointer values to keep buckets compact.
10. Spec Compliance Checklist¶
-
&s[i]used for slice element pointers - No
&m[k]attempts - Map values that need mutation: store pointers
- After
append, refresh pointers if cap may have grown -
*[]Tonly when reassigning the slice header
11. Official Examples¶
Example 1: Slice Element Pointer¶
Example 2: Map of Pointers for Mutation¶
type Counter struct{ N int }
m := map[string]*Counter{"a": {}}
m["a"].N++
m["a"].N++
fmt.Println(m["a"].N) // 2
Example 3: Pointer to Slice for Reassignment¶
Example 4: Stale Pointer After Append¶
s := make([]int, 3, 3)
p := &s[0]
s = append(s, 99) // realloc
*p = 999 // modifies OLD array
fmt.Println(s) // [1 0 0 99] — s sees the new array
12. Related Spec Sections¶
| Section | URL | Relevance |
|---|---|---|
| Slice types | https://go.dev/ref/spec#Slice_types | Slice header, element addressability |
| Map types | https://go.dev/ref/spec#Map_types | Map non-addressability |
| Index expressions | https://go.dev/ref/spec#Index_expressions | s[i], m[k] |
| Address operators | https://go.dev/ref/spec#Address_operators | &s[i] |
| Append | https://go.dev/ref/spec#Appending_and_copying_slices | Realloc semantics |