$npx -y skills add samber/cc-skills-golang --skill golang-safetyDefensive Golang coding to prevent panics, silent data corruption, and subtle runtime bugs. Use when encountering nil panics, append aliasing, map concurrent access, float comparison pitfalls, or zero-value design questions. Also use when reviewing code for nil-safety, numeric co
| 1 | **Persona:** You are a defensive Go engineer. You treat every untested assumption about nil, capacity, and numeric range as a latent crash waiting to happen. |
| 2 | |
| 3 | # Go Safety: Correctness & Defensive Coding |
| 4 | |
| 5 | Prevents programmer mistakes — bugs, panics, and silent data corruption in normal (non-adversarial) code. Security handles attackers; safety handles ourselves. |
| 6 | |
| 7 | ## Best Practices Summary |
| 8 | |
| 9 | 1. **Prefer generics over `any`** when the type set is known — compiler catches mismatches instead of runtime panics |
| 10 | 2. **Always use safe type assertions** — for normal interfaces use comma-ok (`v, ok := x.(T)`); for reflection in Go 1.25+ prefer `reflect.TypeAssert[T](value)` over `value.Interface().(T)`. |
| 11 | 3. **Typed nil pointer in an interface is not `== nil`** — the type descriptor makes it non-nil |
| 12 | 4. **Writing to a nil map panics** — always initialize before use |
| 13 | 5. **`append` may reuse the backing array** — both slices share memory if capacity allows, silently corrupting each other |
| 14 | 6. **Return defensive copies** from exported functions — otherwise callers mutate your internals |
| 15 | 7. **`defer` runs at function exit, not loop iteration** — extract loop body to a function |
| 16 | 8. **Integer conversions truncate silently** — `int64` to `int32` wraps without error |
| 17 | 9. **Float arithmetic is not exact** — use epsilon comparison or `math/big` |
| 18 | 10. **Design useful zero values** — nil map fields panic on first write; use lazy init |
| 19 | 11. **Use `sync.Once` for lazy init** — guarantees exactly-once even under concurrency |
| 20 | |
| 21 | ## Nil Safety |
| 22 | |
| 23 | Nil-related panics are the most common crash in Go. |
| 24 | |
| 25 | ### The nil interface trap |
| 26 | |
| 27 | Interfaces store (type, value). An interface is `nil` only when both are nil. Returning a typed nil pointer sets the type descriptor, making it non-nil: |
| 28 | |
| 29 | ```go |
| 30 | // ✗ Dangerous — interface{type: *MyHandler, value: nil} is not == nil |
| 31 | func getHandler() http.Handler { |
| 32 | var h *MyHandler // nil pointer |
| 33 | if !enabled { |
| 34 | return h // interface{type: *MyHandler, value: nil} != nil |
| 35 | } |
| 36 | return h |
| 37 | } |
| 38 | |
| 39 | // ✓ Good — return nil explicitly |
| 40 | func getHandler() http.Handler { |
| 41 | if !enabled { |
| 42 | return nil // interface{type: nil, value: nil} == nil |
| 43 | } |
| 44 | return &MyHandler{} |
| 45 | } |
| 46 | ``` |
| 47 | |
| 48 | ### Nil map, slice, and channel behavior |
| 49 | |
| 50 | | Type | Index into nil | Write to nil | Len/Cap of nil | Range over nil | |
| 51 | | ------- | -------------- | -------------- | -------------- | -------------- | |
| 52 | | Map | Zero value | **panic** | 0 | 0 iterations | |
| 53 | | Slice | **panic** | **panic** | 0 | 0 iterations | |
| 54 | | Channel | Blocks forever | Blocks forever | 0 | Blocks forever | |
| 55 | |
| 56 | ```go |
| 57 | // ✗ Bad — nil map panics on write |
| 58 | var m map[string]int |
| 59 | m["key"] = 1 |
| 60 | |
| 61 | // ✓ Good — initialize or lazy-init in methods |
| 62 | m := make(map[string]int) |
| 63 | |
| 64 | func (r *Registry) Add(name string, val int) { |
| 65 | if r.items == nil { r.items = make(map[string]int) } |
| 66 | r.items[name] = val |
| 67 | } |
| 68 | ``` |
| 69 | |
| 70 | See **[Nil Safety Deep Dive](./references/nil-safety.md)** for nil receivers, nil in generics, and nil interface performance. |
| 71 | |
| 72 | ## Slice & Map Safety |
| 73 | |
| 74 | ### Slice aliasing — the append trap |
| 75 | |
| 76 | `append` reuses the backing array if capacity allows. Both slices then share memory: |
| 77 | |
| 78 | ```go |
| 79 | // ✗ Dangerous — a and b share backing array |
| 80 | a := make([]int, 3, 5) |
| 81 | b := append(a, 4) |
| 82 | b[0] = 99 // also modifies a[0] |
| 83 | |
| 84 | // ✓ Good — full slice expression forces new allocation |
| 85 | b := append(a[:len(a):len(a)], 4) |
| 86 | ``` |
| 87 | |
| 88 | ### Map concurrent access |
| 89 | |
| 90 | Maps MUST NOT be accessed concurrently — → see `samber/cc-skills-golang@golang-concurrency` for sync primitives. |
| 91 | |
| 92 | See **[Slice and Map Deep Dive](./references/slice-map-safety.md)** for range pitfalls, subslice memory retention, and `slices.Clone`/`maps.Clone`. |
| 93 | |
| 94 | ## Numeric Safety |
| 95 | |
| 96 | ### Implicit type conversions truncate silently |
| 97 | |
| 98 | ```go |
| 99 | // ✗ Bad — silently wraps around if val > math.MaxInt32 (3B becomes -1.29B) |
| 100 | var val int64 = 3_000_000_000 |
| 101 | i32 := int32(val) // -1294967296 (silent wraparound) |
| 102 | |
| 103 | // ✓ Good — check before converting |
| 104 | if val > math.MaxInt32 || val < math.MinInt32 { |
| 105 | return fmt.Errorf("value %d overflows int32", val) |
| 106 | } |
| 107 | i32 := int32(val) |
| 108 | ``` |
| 109 | |
| 110 | ### Float comparison |
| 111 | |
| 112 | ```go |
| 113 | // ✗ Bad — floating point |