From go-agent-skills
Correct use of Go's built-in data structures: slices (nil vs empty, append semantics, aliasing, preallocation), maps (comma-ok, sets, iteration order), arrays, and choosing between them. Use when: "slice vs array", "nil slice", "empty slice", "preallocate", "map iteration", "use a set in Go", "slice aliasing", "append gotcha", "copy a slice", "sync.Map or mutex". Do NOT use for: protecting structures shared across goroutines (use go-concurrency-review), allocation profiling (use go-performance-review), or generic container design (use go-design-patterns).
How this skill is triggered — by the user, by Claude, or both
Slash command
/go-agent-skills:go-data-structuresThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Slices and maps look simple and hide the sharpest edges in the language.
Slices and maps look simple and hide the sharpest edges in the language. These rules prevent the aliasing, nil, and iteration bugs that survive code review.
var a []int // nil slice — len 0, cap 0, no allocation
b := []int{} // empty slice — len 0, allocated header
c := make([]int, 0) // empty slice — same as b
len, cap, range, and append treat all three identically.nil marshals to null, empty marshals to [].
If the API contract requires [], return an empty slice explicitly.len(s) == 0.append MAY return the same backing array or a new one. Both cases
bite:
// ❌ Bad — result may alias the input
func addSuffix(base []string) []string {
return append(base, "suffix") // if cap(base) > len(base),
} // this WRITES INTO base's array
// ✅ Good — force a copy when the input must not be touched
func addSuffix(base []string) []string {
out := make([]string, len(base), len(base)+1)
copy(out, base)
return append(out, "suffix")
}
// ❌ Bad — subslice keeps the whole 64 MB alive
func header(big []byte) []byte {
return big[:512] // backing array is still the full big
}
// ✅ Good — copy the window you keep
func header(big []byte) []byte {
return slices.Clone(big[:512]) // Go 1.21+; or copy() manually
}
Rule: a function either owns a slice or copies it. Returning a subslice of a caller's slice, or appending to one, silently shares memory.
When the final size is known or bounded, allocate once:
// ✅ Good — one allocation
names := make([]string, 0, len(users))
for _, u := range users {
names = append(names, u.Name)
}
// ❌ Bad — repeated growth and copying
var names []string
for _, u := range users {
names = append(names, u.Name)
}
Same for maps: make(map[string]int, len(items)).
Don't preallocate when the size is unknown — a wrong large cap wastes
memory; append growth is fine for cold paths.
// Comma-ok distinguishes "missing" from "zero value"
count, ok := hits[key]
if !ok { /* key absent */ }
// Zero value reads are safe; writes to a nil map PANIC
var m map[string]int
_ = m["x"] // 0, fine
m["x"] = 1 // panic: assignment to entry in nil map — make() first
// Iteration order is RANDOM and differs between runs.
// Sort keys when output must be deterministic:
keys := slices.Sorted(maps.Keys(m)) // Go 1.23+
for _, k := range keys {
fmt.Println(k, m[k])
}
m[k].Field = v doesn't compile for
struct values. Use a map of pointers, or read-modify-write.range is safe; inserting during range is
unspecified (the new key may or may not be visited).The idiomatic set is a map with empty-struct values:
seen := make(map[string]struct{}, len(items))
for _, it := range items {
if _, dup := seen[it.ID]; dup {
continue
}
seen[it.ID] = struct{}{}
process(it)
}
struct{} occupies zero bytes; map[string]bool also works and reads
better when you'll test membership with if seen[id].
[4]byte) are values: assignment and passing copy the whole
array. Comparable with == when elements are comparable.[32]byte), IPv4 addresses, fixed matrices, map keys.[100]int copies 800
bytes per call — almost always wrong.| Need | Use |
|---|---|
| Ordered collection, growable | []T |
| Membership / dedup | map[K]struct{} |
| Key→value lookup | map[K]V |
| Fixed size, value semantics, comparable | [N]T array |
| FIFO queue (single goroutine) | slice with head index, or container/list for heavy churn |
| Stack | slice + append / s[:len(s)-1] |
| Concurrent map, write-once read-many keys | sync.Map — otherwise mutex + map |
sync.Map is a special-case tool (append-only caches, disjoint key
sets). Default to map + sync.RWMutex; see the concurrency skill for
locking patterns.
len() used for emptiness[]append to a slice the function doesn't own; copies made explicitslices.Clone/copymap[K]struct{} (or map[K]bool for readability)npx claudepluginhub eduardo-sl/go-agent-skillsGuides Go developers on idiomatic usage of slices, maps, and arrays including allocation choices, nil vs empty slices, and implementing sets with maps.
Covers Go data structure internals and selection: slices, maps, arrays, container/heap/list/ring, strings.Builder vs bytes.Buffer, generic collections, unsafe.Pointer, weak.Pointer. Use when optimizing or choosing.
Go language conventions, idioms, and toolchain. Invoke when task involves any interaction with Go code — writing, reviewing, refactoring, debugging, or understanding Go projects.