golang-safety
samber/cc-skills-golang
Defensive Go coding: prevent nil panics, slice aliasing, numeric truncation, and resource leaks.
What is golang-safety?
This skill helps you write safer Go code by catching common programmer mistakes—nil pointer dereferences, typed-nil interfaces, append backing-array aliasing, silent numeric conversions, and resource lifecycle bugs. Use it when reviewing code for correctness, designing safe zero values, or debugging nil-related panics.
- Identify nil interface traps and typed-nil pointer returns
- Detect slice aliasing and unsafe append patterns
- Catch silent integer truncation and float comparison errors
- Find defer-in-loop resource accumulation bugs
- Validate nil-safe map and slice initialization
- Design types with safe zero values
How to install golang-safety
npx skills add https://github.com/samber/cc-skills-golang --skill golang-safety- Go compiler (go) installed
- golangci-lint (optional, for automated linting)
How to use golang-safety
- 1.Review the nil safety section for interface and map/slice initialization patterns
- 2.Check numeric conversions for overflow risk using math.MaxInt32/MinInt32 bounds
- 3.Extract loop bodies to separate functions if using defer inside loops
- 4.Use slices.Clone() and maps.Clone() when returning internal collections
- 5.Design zero values that are safe—avoid nil map fields without lazy init
- 6.Use sync.Once for lazy initialization of expensive resources
Use cases
- Fixing a panic from writing to a nil map or dereferencing a nil pointer
- Reviewing code for nil-safety before production deployment
- Designing a struct whose zero value must be safe to use
- Debugging silent data corruption from shared slice backing arrays
- Catching numeric overflow from int64-to-int32 conversions
- Go developers writing production code
- Code reviewers checking for correctness bugs
- Developers designing reusable libraries or APIs
- Teams adopting defensive coding practices
golang-safety FAQ
Interfaces store both a type descriptor and a value. A typed nil pointer (e.g., (*MyHandler)(nil)) sets the type descriptor, making the interface non-nil even though the pointer is nil. Return nil explicitly instead.
When append reuses the backing array because capacity allows, both the original and new slice share memory. Use the full slice expression a[:len(a):len(a)] to force a new allocation.
Go does not error on type conversion overflow. int64 to int32 conversion wraps silently if the value exceeds math.MaxInt32. Always check bounds before converting.
defer runs at function exit, not loop iteration. All deferred cleanup accumulates until the function returns. Extract the loop body to a separate function so defer runs per iteration.
Avoid nil map fields (panic on write), use lazy init with sync.Once, or initialize collections in constructors. Design so var x MyType is immediately usable without additional setup.
Full instructions (SKILL.md)
Source of truth, from samber/cc-skills-golang.
name: golang-safety
description: "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 write or nil pointer dereference, when reviewing code for nil-safety, numeric conversion overflow, or resource lifecycle, or when designing a type whose zero value must be safe. Not for designing concurrent access with goroutines, channels, or sync primitives (→ See samber/cc-skills-golang@golang-concurrency skill), not for exploitable vulnerabilities such as injection, weak crypto, or leaked secrets (→ See samber/cc-skills-golang@golang-security skill), and not for debugging an already-failing program (→ See samber/cc-skills-golang@golang-troubleshooting skill)."
user-invocable: true
license: MIT
compatibility: Designed for Claude Code, Codex or similar harness, and for projects using Golang.
metadata:
author: samber
version: "1.3.2"
openclaw:
emoji: "🛡"
homepage: https://github.com/samber/cc-skills-golang
requires:
bins:
- go
install: []
allowed-tools: Read Edit Write Glob Grep Bash(go:) Bash(golangci-lint:) Bash(git:*) Agent
paths:
- "**/*.go"
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
Related skills
More from samber/cc-skills-golang and the wider catalog.

golang-samber-do
Type-safe dependency injection for Go using samber/do with generics, scopes, and lifecycle management.

golang-samber-hot
Type-safe in-memory caching for Go with 9 eviction algorithms, TTL, loaders, and Prometheus metrics.

golang-samber-lo
500+ type-safe functional helpers for Go slices, maps, and channels — Map, Filter, Reduce, GroupBy, and more.

golang-samber-mo
Monadic types for Go — Option, Result, Either, and more for type-safe nullable values and functional error handling.

golang-samber-oops
Structured error handling for Go with stack traces, error codes, and APM-friendly attributes.

golang-samber-ro
Reactive streams and event-driven programming in Go with 150+ type-safe operators, subjects, and plugins.