Loading...
Loading...
Defensive Golang coding to prevent panics, silent data corruption, and subtle runtime bugs. Use whenever writing or reviewing Go code that involves nil-prone types (pointers, interfaces, maps, slices, channels), numeric conversions, resource lifecycle (defer in loops), or defensive copying. Also triggers on questions about nil panics, append aliasing, map concurrent access, float comparison, or zero-value design.
npx skill4agent add samber/cc-skills-golang golang-safetyany== nilappenddeferint64int32math/bigsync.Oncenil// ✗ Dangerous — interface{type: *MyHandler, value: nil} is not == nil
func getHandler() http.Handler {
var h *MyHandler // nil pointer
if !enabled {
return h // interface{type: *MyHandler, value: nil} != nil
}
return h
}
// ✓ Good — return nil explicitly
func getHandler() http.Handler {
if !enabled {
return nil // interface{type: nil, value: nil} == nil
}
return &MyHandler{}
}| Type | Read from nil | Write to nil | Len/Cap of nil | Range over nil |
|---|---|---|---|---|
| Map | Zero value | panic | 0 | 0 iterations |
| Slice | panic (index) | panic (index) | 0 | 0 iterations |
| Channel | Blocks forever | Blocks forever | 0 | Blocks forever |
// ✗ Bad — nil map panics on write
var m map[string]int
m["key"] = 1
// ✓ Good — initialize or lazy-init in methods
m := make(map[string]int)
func (r *Registry) Add(name string, val int) {
if r.items == nil { r.items = make(map[string]int) }
r.items[name] = val
}append// ✗ Dangerous — a and b share backing array
a := make([]int, 3, 5)
b := append(a, 4)
b[0] = 99 // also modifies a[0]
// ✓ Good — full slice expression forces new allocation
b := append(a[:len(a):len(a)], 4)samber/cc-skills-golang@golang-concurrencyslices.Clonemaps.Clone// ✗ Bad — silently wraps around if val > math.MaxInt32 (3B becomes -1.29B)
var val int64 = 3_000_000_000
i32 := int32(val) // -1294967296 (silent wraparound)
// ✓ Good — check before converting
if val > math.MaxInt32 || val < math.MinInt32 {
return fmt.Errorf("value %d overflows int32", val)
}
i32 := int32(val)// ✗ Bad — floating point arithmetic is not exact
0.1+0.2 == 0.3 // false
// ✓ Good — use epsilon comparison
const epsilon = 1e-9
math.Abs((0.1+0.2)-0.3) < epsilon // true+Inf-InfNaNfunc avg(total, count int) (int, error) {
if count == 0 {
return 0, errors.New("division by zero")
}
return total / count, nil
}samber/cc-skills-golang@golang-securitydefer// ✗ Bad — all files stay open until function returns
for _, path := range paths {
f, _ := os.Open(path)
defer f.Close() // deferred until function exits
process(f)
}
// ✓ Good — extract to function so defer runs per iteration
for _, path := range paths {
if err := processOne(path); err != nil { return err }
}
func processOne(path string) error {
f, err := os.Open(path)
if err != nil { return err }
defer f.Close()
return process(f)
}samber/cc-skills-golang@golang-concurrency// ✗ Bad — exported slice field, anyone can mutate
type Config struct {
Hosts []string
}
// ✓ Good — unexported field with accessor returning a copy
type Config struct {
hosts []string
}
func (c *Config) Hosts() []string {
return slices.Clone(c.hosts)
}var x MyTypevar mu sync.Mutex // ✓ usable at zero value
var buf bytes.Buffer // ✓ usable at zero value
// ✗ Bad — nil map panics on write
type Cache struct { data map[string]any }type DB struct {
once sync.Once
conn *sql.DB
}
func (db *DB) connection() *sql.DB {
db.once.Do(func() {
db.conn, _ = sql.Open("postgres", connStr)
})
return db.conn
}samber/cc-skills-golang@golang-design-patternserrcheckforcetypeassertnilerrgovetstaticchecksamber/cc-skills-golang@golang-lintersamber/cc-skills-golang@golang-concurrencysamber/cc-skills-golang@golang-data-structuressamber/cc-skills-golang@golang-error-handlingsamber/cc-skills-golang@golang-securitysamber/cc-skills-golang@golang-troubleshooting| Mistake | Fix |
|---|---|
Bare type assertion | Panics on type mismatch, crashing the program. Use |
| Returning typed nil in interface function | Interface holds (type, nil) which is != nil. Return untyped |
| Writing to a nil map | Nil maps have no backing storage — write panics. Initialize with |
Assuming | If capacity allows, both slices share the backing array. Use |
| |
| Values wrap silently (3B → -1.29B). Check against |
Comparing floats with | IEEE 754 representation is not exact ( |
| Integer division without zero check | Integer division by zero panics. Guard with |
| Returning internal slice/map reference | Callers can mutate your struct's internals through the shared backing array. Return a defensive copy |
Multiple | |
| Blocking forever on nil channel | Nil channels block on both send and receive. Always initialize before use |