Claude-skill-registry collections
Reviews Go slice and map usage for nil semantics, capacity hints, and iteration patterns. Use when reviewing collection operations, seeing slice/map initialization, or encountering nil panics.
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/collections" ~/.claude/skills/majiayu000-claude-skill-registry-collections && rm -rf "$T"
manifest:
skills/data/collections/SKILL.mdsource content
Collections
Purpose
Establish patterns for working with slices and maps in RMS Go code. Understanding collection semantics prevents common bugs and improves performance.
Core Principles
- Understand nil semantics - Nil slices and maps behave differently
- Use capacity hints - Pre-allocate when size is known
- Prefer range - For iteration over index-based access
- Consider thread safety - Maps are not safe for concurrent access
Slices
Nil vs Empty Slice
// Nil slice - default zero value var tasks []*Task // nil slice len(tasks) // 0 cap(tasks) // 0 tasks == nil // true // Empty slice - explicitly allocated tasks := []*Task{} // empty slice tasks := make([]*Task, 0) // empty slice len(tasks) // 0 tasks == nil // false // Behavior difference json.Marshal(nil) // null json.Marshal([]*Task{}) // []
When to Use Each
// DO: Use nil slice as return value when appropriate func (s *Store) List(ctx context.Context) ([]*Task, error) { // Returns nil when no results - this is fine return nil, nil } // DO: Use empty slice for JSON serialization func (h *Handler) ListTasks(w http.ResponseWriter, r *http.Request) { tasks, err := store.List(r.Context()) if tasks == nil { tasks = []*Task{} // Serialize as [] not null } json.NewEncoder(w).Encode(tasks) } // DO: Check length, not nil if len(tasks) == 0 { // Works for both nil and empty } // DON'T: Check nil when you mean empty if tasks == nil { // Misses empty slices // ... }
Capacity Hints
// DO: Pre-allocate when size is known func convertTasks(items []*Item) []*Task { tasks := make([]*Task, 0, len(items)) for _, item := range items { tasks = append(tasks, convertTask(item)) } return tasks } // DO: Pre-allocate with make for known size func getIDs(tasks []*Task) []rms.ID { ids := make([]rms.ID, len(tasks)) for i, task := range tasks { ids[i] = task.ID } return ids } // DON'T: Grow slice repeatedly func convertTasks(items []*Item) []*Task { var tasks []*Task // Starts at 0 capacity for _, item := range items { tasks = append(tasks, convertTask(item)) // Multiple reallocations } return tasks }
Slice Operations
// Append tasks = append(tasks, newTask) tasks = append(tasks, moreTasks...) // Copy dst := make([]*Task, len(src)) copy(dst, src) // Delete (preserving order) tasks = append(tasks[:i], tasks[i+1:]...) // Delete (no order preservation, more efficient) tasks[i] = tasks[len(tasks)-1] tasks = tasks[:len(tasks)-1] // Filter in place n := 0 for _, task := range tasks { if task.IsValid() { tasks[n] = task n++ } } tasks = tasks[:n]
Maps
Nil Map Behavior
// Nil map - read OK, write panics var m map[string]int // nil v := m["key"] // Returns 0 (zero value), no panic m["key"] = 1 // PANIC: assignment to nil map // Always initialize before writing m = make(map[string]int) m["key"] = 1 // OK // Or use map literal m := map[string]int{ "key": 1, }
Capacity Hints
// DO: Hint capacity for large maps func buildIndex(tasks []*Task) map[rms.ID]*Task { index := make(map[rms.ID]*Task, len(tasks)) for _, task := range tasks { index[task.ID] = task } return index } // DON'T: No hint when size is known func buildIndex(tasks []*Task) map[rms.ID]*Task { index := make(map[rms.ID]*Task) // Grows repeatedly for _, task := range tasks { index[task.ID] = task } return index }
Comma-Ok Idiom
// DO: Check existence with comma-ok if task, ok := taskMap[id]; ok { // task exists process(task) } else { // task doesn't exist } // DO: Distinguish zero value from missing count, exists := counts[key] if !exists { counts[key] = 1 } else { counts[key] = count + 1 } // DON'T: Assume zero means missing count := counts[key] if count == 0 { // Bug: 0 could be a valid value // ... }
Map Iteration
// DO: Iterate with range for key, value := range m { fmt.Printf("%s: %v\n", key, value) } // DO: Keys only for key := range m { keys = append(keys, key) } // CAUTION: Iteration order is random // If you need ordered iteration, sort keys first keys := make([]string, 0, len(m)) for k := range m { keys = append(keys, k) } sort.Strings(keys) for _, k := range keys { fmt.Printf("%s: %v\n", k, m[k]) }
Safe Deletion During Iteration
// DO: Delete during iteration is safe for k, v := range m { if shouldDelete(v) { delete(m, k) // Safe } } // DON'T: Add during iteration (undefined behavior) for k := range m { m[k+"_copy"] = m[k] // May or may not see new keys }
Thread Safety
Maps Are Not Thread-Safe
// DON'T: Concurrent map access var cache = make(map[string]*Task) func get(key string) *Task { return cache[key] // Data race! } func set(key string, task *Task) { cache[key] = task // Data race! }
Use sync.Map for Concurrent Access
// DO: sync.Map for concurrent access var cache sync.Map func get(key string) (*Task, bool) { if v, ok := cache.Load(key); ok { return v.(*Task), true } return nil, false } func set(key string, task *Task) { cache.Store(key, task) } func getOrCreate(key string, create func() *Task) *Task { v, _ := cache.LoadOrStore(key, create()) return v.(*Task) }
Mutex-Protected Map
// DO: Mutex for complex operations type TaskCache struct { mu sync.RWMutex tasks map[rms.ID]*Task } func (c *TaskCache) Get(id rms.ID) (*Task, bool) { c.mu.RLock() defer c.mu.RUnlock() task, ok := c.tasks[id] return task, ok } func (c *TaskCache) Set(id rms.ID, task *Task) { c.mu.Lock() defer c.mu.Unlock() c.tasks[id] = task } func (c *TaskCache) GetOrSet(id rms.ID, create func() *Task) *Task { c.mu.Lock() defer c.mu.Unlock() if task, ok := c.tasks[id]; ok { return task } task := create() c.tasks[id] = task return task }
Common Patterns
Grouping
// Group items by key func groupByStatus(tasks []*Task) map[Status][]*Task { groups := make(map[Status][]*Task) for _, task := range tasks { groups[task.Status] = append(groups[task.Status], task) } return groups } // Group by workflow with capacity hint func groupByWorkflow(tasks []*Task) map[rms.ID][]*Task { groups := make(map[rms.ID][]*Task, len(tasks)/10) // Estimate for _, task := range tasks { groups[task.WorkflowID] = append(groups[task.WorkflowID], task) } return groups }
Deduplication
// Deduplicate slice func uniqueIDs(ids []rms.ID) []rms.ID { seen := make(map[rms.ID]struct{}, len(ids)) result := make([]rms.ID, 0, len(ids)) for _, id := range ids { if _, ok := seen[id]; !ok { seen[id] = struct{}{} result = append(result, id) } } return result }
Set Operations
// Set using map type StringSet map[string]struct{} func NewStringSet(values ...string) StringSet { s := make(StringSet, len(values)) for _, v := range values { s[v] = struct{}{} } return s } func (s StringSet) Add(value string) { s[value] = struct{}{} } func (s StringSet) Remove(value string) { delete(s, value) } func (s StringSet) Contains(value string) bool { _, ok := s[value] return ok }
Index Building
// Build lookup index func buildTaskIndex(tasks []*Task) map[rms.ID]*Task { index := make(map[rms.ID]*Task, len(tasks)) for _, task := range tasks { index[task.ID] = task } return index } // Multi-index type TaskIndices struct { ByID map[rms.ID]*Task ByWorkflow map[rms.ID][]*Task ByStatus map[Status][]*Task } func buildIndices(tasks []*Task) *TaskIndices { indices := &TaskIndices{ ByID: make(map[rms.ID]*Task, len(tasks)), ByWorkflow: make(map[rms.ID][]*Task), ByStatus: make(map[Status][]*Task), } for _, task := range tasks { indices.ByID[task.ID] = task indices.ByWorkflow[task.WorkflowID] = append(indices.ByWorkflow[task.WorkflowID], task) indices.ByStatus[task.Status] = append(indices.ByStatus[task.Status], task) } return indices }
Quick Reference
| Operation | Slice | Map |
|---|---|---|
| Zero value | (safe to read) | (panics on write) |
| Initialize | | |
| Length | | |
| Add | | |
| Delete | | |
| Check exists | | |
| Thread-safe | No | No (use sync.Map) |
Checklist
- Pre-allocated with known capacity?
- Nil vs empty semantics correct for use case?
- Comma-ok used for map lookups?
- Thread safety considered?
- Range used for iteration?
See Also
- concurrency - Thread-safe patterns
- control-flow - Iteration patterns