Generated banner for Golang Safety
Generated logo for Golang Safety

Golang Safety

Samuel BertheCommunityCreated by samberDistributed by Samuel Berthe

  • Skill
  • Guidelines
  • Software development
  • MIT
  • No issues found

From the author

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).
— the description by samber

AutoGineer summary

Written by AutoGineer from the skill’s own files, not by the author

This skill provides a set of defensive coding practices for Golang to prevent accidental bugs that lead to crashes or silent data corruption. It covers critical areas such as nil-safety, slice and map memory management, numeric type conversions, and resource lifecycle management. By following these guidelines, developers can write more robust Go code that handles zero values safely and avoids common pitfalls like backing-array aliasing or silent integer truncation.

What it does

  • Provides patterns to prevent nil pointer dereferences and nil map panics.
  • Explains how to avoid silent data corruption from slice backing-array aliasing.
  • Offers guidance on safe numeric type conversions to prevent silent truncation.
  • Details correct usage of defer in loops to prevent resource accumulation.
  • Defines patterns for designing types with safe, usable zero values.
  • Highlights the nil interface trap and how to return nil errors correctly.

When to use it

  • When reviewing Go code for potential nil-safety issues or numeric overflows.
  • When designing new types that should be safe to use in their zero-value state.
  • When refactoring code that experiences intermittent panics or unexpected data behavior.
  • When implementing exported functions that need to protect internal state from caller mutation.

Advantages

  • Reduces common runtime panics and crashes in production.
  • Prevents subtle, hard-to-debug data corruption issues.
  • Promotes consistent, defensive coding standards across a codebase.
  • Improves the reliability of exported APIs by enforcing defensive copying.

Usage examples

“How do I safely return an error interface in Go without falling into the typed nil trap?”
The agent will explain that returning a typed nil pointer inside an interface makes the interface non-nil, and will provide the pattern of returning an explicit untyped nil.
“Why is my slice being modified unexpectedly after an append operation?”
The agent will identify the backing-array aliasing issue and demonstrate how to use the full slice expression (s[:len:cap]) to force a new allocation.
“How can I prevent my Go program from panicking when writing to a map?”
The agent will explain that writing to a nil map causes a panic and will provide examples of initializing maps or using lazy-initialization patterns.
“What is the best way to handle resource cleanup inside a loop in Go?”
The agent will explain that defer runs at function exit rather than loop iteration, and will suggest extracting the loop body into a separate function to ensure timely resource release.

Install

  1. Download the skill as a ZIP:

    https://mp-staging.autogineer.com/agent-skills/samber/golang-safety/download
  2. Unzip it into the cross-client skills folder of your project:

    mkdir -p .agents/skills && unzip golang-safety.zip -d .agents/skills

Documentation by samber

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 safe type assertions — for normal interfaces use comma-ok (v, ok := x.(T)); for reflection in Go 1.25+ prefer reflect.TypeAssert[T](value) over value.Interface().(T).

  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
TypeIndex into nilWrite to nilLen/Cap of nilRange over nil
MapZero valuepanic00 iterations
Slicepanicpanic00 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
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

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 copiesIf capacity allows, both slices share the backing array. Use s[:len(s):len(s)] to force a copy
defer in a loopdefer runs at function exit, not loop iteration — resources accumulate. Extract body to a separate function
int64 to int32 without bounds checkValues 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 assumptionsinit() 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

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

  • → See samber/cc-skills-golang@golang-continuous-integration skill for automated AI-driven code review in CI using these guidelines

Files

Requirements and compatibility

Written for: Claude Code, OpenAI Codex

Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta

“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.

Needs: Declares allowed tools, Claude Code frontmatter

The author says: Designed for Claude Code, Codex or similar harness, and for projects using Golang.

Security

No issues found by our static scan· No issues found

A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.

Attribution and licence

Created by
samber
Publisher
Samuel Berthe (Community)
Distributed by
Samuel Berthe
Source
samber/cc-skills-golang · skills/golang-safety · commit bb1d43d
Licence
MIT License (redistribution allowed)
  • LICENSE (added by AutoGineer from LICENSE)
  • ATTRIBUTION.md (written by AutoGineer)
Synced

AutoGineer is not affiliated with or endorsed by samber or Samuel Berthe.

Download ZIP

The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download golang-safety.zip