Skillshub golang-safety

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.

install
source · Clone the upstream repo
git clone https://github.com/ComeOnOliver/skillshub
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/ComeOnOliver/skillshub "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/Harmeet10000/skills/golang-safety" ~/.claude/skills/comeonoliver-skillshub-golang-safety && rm -rf "$T"
manifest: skills/Harmeet10000/skills/golang-safety/SKILL.md
source content

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.

Go Safety: Correctness & Defensive Coding

Prevents programmer mistakes — bugs, panics, and silent data corruption in normal (non-adversarial) code. Security handles attackers; safety handles ourselves.

Best Practices Summary

  1. Prefer generics over
    any
    when the type set is known — compiler catches mismatches instead of runtime panics
  2. Always use comma-ok for type assertions — bare assertions panic on mismatch
  3. Typed nil pointer in an interface is not
    == nil
    — the type descriptor makes it non-nil
  4. Writing to a nil map panics — always initialize before use
  5. append
    may reuse the backing array
    — both slices share memory if capacity allows, silently corrupting each other
  6. Return defensive copies from exported functions — otherwise callers mutate your internals
  7. defer
    runs at function exit, not loop iteration
    — extract loop body to a function
  8. Integer conversions truncate silently
    int64
    to
    int32
    wraps without error
  9. Float arithmetic is not exact — use epsilon comparison or
    math/big
  10. Design useful zero values — nil map fields panic on first write; use lazy init
  11. Use
    sync.Once
    for lazy init
    — guarantees exactly-once even under concurrency

Nil Safety

Nil-related panics are the most common crash in Go.

The nil interface trap

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:

// ✗ 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{}
}

Nil map, slice, and channel behavior

TypeRead from nilWrite to nilLen/Cap of nilRange over nil
MapZero valuepanic00 iterations
Slicepanic (index)panic (index)00 iterations
ChannelBlocks foreverBlocks forever0Blocks 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
}

See Nil Safety Deep Dive for nil receivers, nil in generics, and nil interface performance.

Slice & Map Safety

Slice aliasing — the append trap

append
reuses the backing array if capacity allows. Both slices then share memory:

// ✗ 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)

Map concurrent access

Maps MUST NOT be accessed concurrently — → see

samber/cc-skills-golang@golang-concurrency
for sync primitives.

See Slice and Map Deep Dive for range pitfalls, subslice memory retention, and

slices.Clone
/
maps.Clone
.

Numeric Safety

Implicit type conversions truncate silently

// ✗ 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)

Float comparison

// ✗ 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

Division by zero

Integer division by zero panics. Float division by zero produces

+Inf
,
-Inf
, or
NaN
.

func avg(total, count int) (int, error) {
    if count == 0 {
        return 0, errors.New("division by zero")
    }
    return total / count, nil
}

For integer overflow as a security vulnerability, see the

samber/cc-skills-golang@golang-security
skill section.

Resource Safety

defer in loops — resource accumulation

defer
runs at function exit, not loop iteration. Resources accumulate until the function returns:

// ✗ 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)
}

Goroutine leaks

→ See

samber/cc-skills-golang@golang-concurrency
for goroutine lifecycle and leak prevention.

Immutability & Defensive Copying

Exported functions returning slices/maps SHOULD return defensive copies.

Protecting struct internals

// ✗ 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)
}

Initialization Safety

Zero-value design

Design types so

var x MyType
is safe — prevents "forgot to initialize" bugs:

var 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 }

sync.Once for lazy initialization

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
}

init() function pitfalls

→ See

samber/cc-skills-golang@golang-design-patterns
for why init() should be avoided in favor of explicit constructors.

Enforce with Linters

Many safety pitfalls are caught automatically by linters:

errcheck
,
forcetypeassert
,
nilerr
,
govet
,
staticcheck
. See the
samber/cc-skills-golang@golang-linter
skill for configuration and usage.

Cross-References

  • → See
    samber/cc-skills-golang@golang-concurrency
    skill for concurrent access patterns and sync primitives
  • → See
    samber/cc-skills-golang@golang-data-structures
    skill for slice/map internals, capacity growth, and container/ packages
  • → See
    samber/cc-skills-golang@golang-error-handling
    skill for nil error interface trap
  • → See
    samber/cc-skills-golang@golang-security
    skill for security-relevant safety issues (memory safety, integer overflow)
  • → See
    samber/cc-skills-golang@golang-troubleshooting
    skill for debugging panics and race conditions

Common Mistakes

MistakeFix
Bare type assertion
v := x.(T)
Panics on type mismatch, crashing the program. Use
v, ok := x.(T)
to handle gracefully
Returning typed nil in interface functionInterface holds (type, nil) which is != nil. Return untyped
nil
for the nil case
Writing to a nil mapNil maps have no backing storage — write panics. Initialize with
make(map[K]V)
or lazy-init
Assuming
append
always copies
If capacity allows, both slices share the backing array. Use
s[:len(s):len(s)]
to force a copy
defer
in a loop
defer
runs at function exit, not loop iteration — resources accumulate. Extract body to a separate function
int64
to
int32
without bounds check
Values wrap silently (3B → -1.29B). Check against
math.MaxInt32
/
math.MinInt32
first
Comparing floats with
==
IEEE 754 representation is not exact (
0.1+0.2 != 0.3
). Use
math.Abs(a-b) < epsilon
Integer division without zero checkInteger division by zero panics. Guard with
if divisor == 0
before dividing
Returning internal slice/map referenceCallers can mutate your struct's internals through the shared backing array. Return a defensive copy
Multiple
init()
with ordering assumptions
init()
execution order across files is unspecified. → See
samber/cc-skills-golang@golang-design-patterns
— use explicit constructors
Blocking forever on nil channelNil channels block on both send and receive. Always initialize before use