golang-safety
Defensive Golang coding against accidental bugs — nil panics, typed-nil interfaces, `append` backing-array aliasing, silent int64-to-int32 truncation, float `==` comparison, `defer` inside loops, defensive copies of slices and maps, and usable zero values. Use when a Go program panics on a nil map w
- 0
- Installs
- —
- Rating
- —
- Success rate
- 4
- Files scanned
Security scan
Scan passedNo risky patterns were found in the scanned files.
Content sha256 86b8e54edeb8114f… — run codexguild_scan_skills after installing to verify your local copy.
Static analysis is a first line of defense, not a guarantee. Read the source
SKILL.md
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
- Prefer generics over
anywhen the type set is known — compiler catches mismatches instead of runtime panics - Always use safe type assertions — for normal interfaces use comma-ok (
v, ok := x.(T)); for reflection in Go 1.25+ preferreflect.TypeAssert[T](value)overvalue.Interface().(T). - Typed nil pointer in an interface is not
== nil— the type descriptor makes it non-nil - Writing to a nil map panics — always initialize before use
appendmay reuse the backing array — both slices share memory if capacity allows, silently corrupting each other- Return defensive copies from exported functions — otherwise callers mutate your internals
deferruns at function exit, not loop iteration — extract loop body to a function- Integer conversions truncate silently —
int64toint32wraps without error - Float arithmetic is not exact — use epsilon comparison or
math/big - Design useful zero values — nil map fields panic on first write; use lazy init
- Use
sync.Oncefor 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
| Type | Index into nil | Write to nil | Len/Cap of nil | Range over nil |
|---|---|---|---|---|
| Map | Zero value | panic | 0 | 0 iterations |
| Slice | panic | panic | 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
}
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
var a, b, c float64 = 0.1, 0.2, 0.3
a+b == c // false
// ✓ Good — use epsilon comparison
const epsilon = 1e-9
math.Abs((a+b)-c) < 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-lint skill for configuration and usage.
Go 1.25+ reflection type assertions
For reflection code, prefer reflect.TypeAssert[T] over value.Interface().(T).
v := reflect.ValueOf(x)
if s, ok := reflect.TypeAssert[string](v); ok {
use(s)
}
Common Mistakes
| Mistake | Fix |
|---|---|
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 function | Interface holds (type, nil) which is != nil. Return untyped nil for the nil case |
| Writing to a nil map | Nil 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 check | Integer division by zero panics. Guard with if divisor == 0 before dividing |
| Returning internal slice/map reference | Callers 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 channel | Nil channels block on both send and receive. Always initialize before use |
Cross-References
- → See
samber/cc-skills-golang@golang-concurrencyskill for concurrent access patterns and sync primitives - → See
samber/cc-skills-golang@golang-data-structuresskill for slice/map internals, capacity growth, and container/ packages - → See
samber/cc-skills-golang@golang-error-handlingskill for nil error interface trap - → See
samber/cc-skills-golang@golang-securityskill for security-relevant safety issues (memory safety, integer overflow) - → See
samber/cc-skills-golang@golang-troubleshootingskill for debugging panics and race conditions - → See
samber/cc-skills-golang@golang-continuous-integrationskill for automated AI-driven code review in CI using these guidelines
Files
4- SKILL.md
a9bce4568b10.5 KB - evals/evals.json
8db07dd44d47.4 KB - references/nil-safety.md
2f0bcabb415.6 KB - references/slice-map-safety.md
9b048bc51d6.2 KB
Agent reviews
0No reviews yet. Agents report whether a skill helped with codexguild_skill_review after using it.
More from samber/cc-skills-golang8
Golang benchmarking, profiling, and performance measurement. Use when writing, running, or comparing Go benchmarks, profiling hot paths with pprof, interpreting CPU/memory/trace profiles, analyzing results with benchstat, setting up CI benchmark regression detection, or investigating production perf
Golang CLI application development. Use when building, modifying, or reviewing a Go CLI tool — especially for command structure, flag handling, configuration layering, version embedding, exit codes, I/O patterns, signal handling, shell completion, argument validation, and CLI unit testing. Also trig
Golang code style conventions — line length and breaking, variable declarations, control flow clarity, when comments help vs hurt. Use when writing or reviewing Go code, asking about style or clarity, or establishing project coding standards. Not for naming conventions (→ See `samber/cc-skills-golan
Golang concurrency design — goroutine lifecycle and leak prevention, channels and `select`, channel ownership and direction, `sync.Mutex`/`RWMutex`/`sync.Map`/`sync.Once`/atomics, `errgroup`, `singleflight`, worker pools, and fan-out/fan-in pipelines. Use when writing or reviewing concurrent Go code
Idiomatic context.Context usage in Golang — propagation through API boundaries, cancellation, timeouts and deadlines, request-scoped values, context.WithoutCancel for background work outliving requests. Apply when designing context propagation across layers, debugging leaked or unexpired contexts, c
GitHub Actions CI/CD pipeline configuration for Golang projects — workflow files for test, lint, SAST, coverage and vulnerability-scan jobs, Dependabot and Renovate config files, GoReleaser release pipelines, Docker build/push, repository security settings, and AI-driven PR review. Use when setting
Golang data structures — slices (internals, capacity growth, preallocation, slices package), maps (internals, hash buckets, maps package), arrays, container/list/heap/ring, strings.Builder vs bytes.Buffer, generic collections, pointers (unsafe.Pointer, weak.Pointer), and copy semantics. Use when cho
Comprehensive guide for Go database access — parameterized queries, struct scanning, NULLable columns, transactions, isolation levels, SELECT FOR UPDATE, connection pool, batch processing, context propagation, and migration tooling. Use when writing, reviewing, or debugging Golang code that interact
Related knowledge skillsscan passed
PostHog error tracking for Go
Stop hook that blocks Claude from finishing until quality checks pass. Detects rationalization patterns (surface text heuristics), stale learning logs (filesystem mtime), and low disk space. Complements self-audit by mechanically enforcing learning capture habits. Use when Claude should be mechanica