go-safety
Defensive Golang coding to prevent panics, silent data corruption, and subtle runtime bugs.
Files1
View on GitHubSKILL.mdskills/go/go-safety/SKILL.md
GitHubCore Safety Principles
- Defense against Nil — Nil-related panics are the #1 cause of crashes in Go.
- Interface Trap — A typed nil pointer inside an interface is NOT equal to
nil. - Map Initialization — Reading from a nil map is safe (returns zero value), but writing to one PANICS.
- Slice Aliasing —
appendmay reuse the backing array, leading to shared state and silent data corruption. - Defer in Loops —
deferruns at the end of the function, not the loop iteration. This can lead to resource exhaustion. - Numeric Safety — Integer conversions (e.g.,
int64toint32) truncate silently. Always check bounds.
Common Safety Patterns
1. Correct Interface Return
// ✅ Good: Return untyped nil for the nil case
func getHandler(enabled bool) http.Handler {
if !enabled {
return nil
}
return &MyHandler{}
}
// ❌ Bad: Returns interface{type: *MyHandler, value: nil} which != nil
func getHandler(enabled bool) http.Handler {
var h *MyHandler
return h
}
2. Defensive Slice Appending
// Force a copy by limiting capacity of the source slice
b := append(a[:len(a):len(a)], 4)
3. Loop Resource Management
// ✅ Good: Extract to function to ensure defer runs per iteration
for _, path := range paths {
if err := process(path); err != nil { return err }
}
func process(path string) error {
f, err := os.Open(path)
if err != nil { return err }
defer f.Close()
return doSomething(f)
}
4. Safe Type Assertions
// ✅ Good: Use comma-ok pattern
v, ok := i.(string)
if !ok {
// handle error
}
// ❌ Bad: Panics if i is not a string
v := i.(string)
Zero-Value Design
Design your structs so they are usable in their zero state (var x T):
sync.Mutexis usable without initialization.bytes.Bufferis usable without initialization.- Avoid types that panic on first use if not initialized (like uninitialized maps in structs).
Anti-patterns
- ❌ Bare type assertions —
x.(T)withoutok. - ❌ Ignoring integer overflow during conversion.
- ❌ Comparing floats with
==— Use an epsilon value. - ❌ Returning internal slices/maps — Return a copy instead to prevent external mutation.
- ❌ Using
init()for complex setup — Can't return errors and makes testing hard.