golang-safety

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese
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工程师。你将所有关于nil、容量和数值范围的未验证假设都视为潜在的崩溃隐患。

Go Safety: Correctness & Defensive Coding

Go安全:正确性与防御性编码

Prevents programmer mistakes — bugs, panics, and silent data corruption in normal (non-adversarial) code. Security handles attackers; safety handles ourselves.
防止程序员失误——在常规(非对抗性)代码中避免bug、panic和静默数据损坏。安全机制应对攻击者,而安全编码应对我们自己的失误。

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
  1. 已知类型集时优先使用泛型而非
    any
    ——编译器会捕获类型不匹配问题,而非在运行时触发panic
  2. 类型断言始终使用comma-ok模式 ——裸断言在类型不匹配时会触发panic
  3. 接口中的类型化nil指针不等于
    == nil
    ——类型描述符会使其非nil
  4. 向nil map写入会触发panic ——使用前务必初始化
  5. append
    可能复用底层数组
    ——如果容量允许,两个切片会共享内存,导致静默数据损坏
  6. 导出函数返回防御性拷贝 ——否则调用者可以修改你的内部数据
  7. defer
    在函数退出时执行,而非循环迭代时
    ——将循环体提取为独立函数
  8. 整数转换会静默截断 ——
    int64
    int32
    会无错误地溢出
  9. 浮点数运算并非精确 ——使用epsilon比较或
    math/big
  10. 设计实用的零值 ——nil map字段首次写入会触发panic;使用延迟初始化
  11. 使用
    sync.Once
    进行延迟初始化
    ——即使在并发场景下也能保证仅执行一次

Nil Safety

Nil安全

Nil-related panics are the most common crash in Go.
与nil相关的panic是Go中最常见的崩溃原因。

The nil interface trap

nil接口陷阱

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:
go
// ✗ 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时,接口才是
nil
。返回类型化nil指针会设置类型描述符,使其非nil:
go
// ✗ 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

nil map、切片和通道的行为

TypeRead from nilWrite to nilLen/Cap of nilRange over nil
MapZero valuepanic00 iterations
Slicepanic (index)panic (index)00 iterations
ChannelBlocks foreverBlocks forever0Blocks forever
go
// ✗ 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.
类型读取nil值写入nil值nil的长度/容量遍历nil值
Map零值panic00次迭代
Slicepanic(索引访问)panic(索引访问)00次迭代
Channel永久阻塞永久阻塞0永久阻塞
go
// ✗ 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
}
如需了解nil接收器、泛型中的nil以及nil接口性能,请查看**Nil安全深度解析**。

Slice & Map Safety

切片与Map安全

Slice aliasing — the append trap

切片别名——append陷阱

append
reuses the backing array if capacity allows. Both slices then share memory:
go
// ✗ 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)
如果容量允许,
append
会复用底层数组。此时两个切片会共享内存:
go
// ✗ 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

Map并发访问

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
.
Map严禁并发访问——→ 如需了解同步原语,请查看
samber/cc-skills-golang@golang-concurrency
如需了解遍历陷阱、子切片内存保留以及
slices.Clone
/
maps.Clone
,请查看**切片与Map安全深度解析**。

Numeric Safety

数值安全

Implicit type conversions truncate silently

隐式类型转换会静默截断

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

浮点数比较

go
// ✗ 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
go
// ✗ 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
.
go
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.
整数除零会触发panic。浮点数除零会产生
+Inf
-Inf
NaN
go
func avg(total, count int) (int, error) {
    if count == 0 {
        return 0, errors.New("division by zero")
    }
    return total / count, nil
}
如需了解作为安全漏洞的整数溢出,请查看
samber/cc-skills-golang@golang-security
技能章节。

Resource Safety

资源安全

defer in loops — resource accumulation

循环中的defer——资源累积

defer
runs at function exit, not loop iteration. Resources accumulate until the function returns:
go
// ✗ 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)
}
defer
函数退出时执行,而非循环迭代时。资源会累积到函数返回时才释放:
go
// ✗ 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

Goroutine泄漏

→ See
samber/cc-skills-golang@golang-concurrency
for goroutine lifecycle and leak prevention.
→ 如需了解Goroutine生命周期和泄漏预防,请查看
samber/cc-skills-golang@golang-concurrency

Immutability & Defensive Copying

不可变性与防御性拷贝

Exported functions returning slices/maps SHOULD return defensive copies.
返回切片/Map的导出函数应返回防御性拷贝。

Protecting struct internals

保护结构体内部数据

go
// ✗ 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)
}
go
// ✗ 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:
go
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 }
设计类型时应保证
var x MyType
是安全的——避免"忘记初始化"的bug:
go
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

使用sync.Once进行延迟初始化

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

init()函数的陷阱

→ See
samber/cc-skills-golang@golang-design-patterns
for why init() should be avoided in favor of explicit constructors.
→ 如需了解为何应避免使用init()而改用显式构造函数,请查看
samber/cc-skills-golang@golang-design-patterns

Enforce with Linters

使用Linter强制执行

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.
许多安全隐患可以被Linter自动捕获:
errcheck
forcetypeassert
nilerr
govet
staticcheck
。如需了解配置和使用方法,请查看
samber/cc-skills-golang@golang-linter
技能。

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
  • → 如需了解并发访问模式和同步原语,请查看
    samber/cc-skills-golang@golang-concurrency
    技能
  • → 如需了解切片/Map内部实现、容量增长和container/包,请查看
    samber/cc-skills-golang@golang-data-structures
    技能
  • → 如需了解nil错误接口陷阱,请查看
    samber/cc-skills-golang@golang-error-handling
    技能
  • → 如需了解与安全相关的安全问题(内存安全、整数溢出),请查看
    samber/cc-skills-golang@golang-security
    技能
  • → 如需了解调试panic和竞争条件,请查看
    samber/cc-skills-golang@golang-troubleshooting
    技能

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
错误修复方案
裸类型断言
v := x.(T)
类型不匹配时会触发panic导致程序崩溃。使用
v, ok := x.(T)
优雅处理
接口函数返回类型化nil接口存储(类型,nil),不等于nil。nil场景下返回非类型化
nil
向nil map写入nil map没有底层存储——写入会触发panic。使用
make(map[K]V)
初始化或延迟初始化
假设
append
始终会拷贝
如果容量允许,两个切片会共享底层数组。使用
s[:len(s):len(s)]
强制拷贝
循环中的
defer
defer
在函数退出时执行,而非循环迭代时——资源会累积。将循环体提取为独立函数
int64
int32
不做边界检查
值会静默溢出(30亿→-12.94亿)。先与
math.MaxInt32
/
math.MinInt32
进行检查
使用
==
比较浮点数
IEEE 754表示法并非精确(
0.1+0.2 != 0.3
)。使用
math.Abs(a-b) < epsilon
整数除法不做零检查整数除零会触发panic。除法前用
if divisor == 0
进行判断
返回内部切片/Map引用调用者可以通过共享底层数组修改结构体的内部数据。返回防御性拷贝
多个
init()
依赖执行顺序假设
跨文件的
init()
执行顺序未定义。→ 查看
samber/cc-skills-golang@golang-design-patterns
——使用显式构造函数
在nil通道上永久阻塞nil通道在发送和接收时都会阻塞。使用前务必初始化
",