golang-safety
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChinesePersona: 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
最佳实践总结
- Prefer generics over when the type set is known — compiler catches mismatches instead of runtime panics
any - Always use comma-ok for type assertions — bare assertions panic on mismatch
- Typed nil pointer in an interface is not — the type descriptor makes it non-nil
== nil - Writing to a nil map panics — always initialize before use
- may reuse the backing array — both slices share memory if capacity allows, silently corrupting each other
append - Return defensive copies from exported functions — otherwise callers mutate your internals
- runs at function exit, not loop iteration — extract loop body to a function
defer - Integer conversions truncate silently — to
int64wraps without errorint32 - 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 for lazy init — guarantees exactly-once even under concurrency
sync.Once
- 已知类型集时优先使用泛型而非——编译器会捕获类型不匹配问题,而非在运行时触发panic
any - 类型断言始终使用comma-ok模式 ——裸断言在类型不匹配时会触发panic
- 接口中的类型化nil指针不等于——类型描述符会使其非nil
== nil - 向nil map写入会触发panic ——使用前务必初始化
- 可能复用底层数组 ——如果容量允许,两个切片会共享内存,导致静默数据损坏
append - 导出函数返回防御性拷贝 ——否则调用者可以修改你的内部数据
- 在函数退出时执行,而非循环迭代时 ——将循环体提取为独立函数
defer - 整数转换会静默截断 ——转
int64会无错误地溢出int32 - 浮点数运算并非精确 ——使用epsilon比较或包
math/big - 设计实用的零值 ——nil map字段首次写入会触发panic;使用延迟初始化
- 使用进行延迟初始化 ——即使在并发场景下也能保证仅执行一次
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 only when both are nil. Returning a typed nil pointer sets the type descriptor, making it non-nil:
nilgo
// ✗ 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:
nilgo
// ✗ 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、切片和通道的行为
| Type | Read from nil | Write to nil | Len/Cap of nil | Range over nil |
|---|---|---|---|---|
| Map | Zero value | panic | 0 | 0 iterations |
| Slice | panic (index) | panic (index) | 0 | 0 iterations |
| Channel | Blocks forever | Blocks forever | 0 | Blocks 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 | 零值 | panic | 0 | 0次迭代 |
| Slice | panic(索引访问) | panic(索引访问) | 0 | 0次迭代 |
| 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陷阱
appendgo
// ✗ 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)如果容量允许,会复用底层数组。此时两个切片会共享内存:
appendgo
// ✗ 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 for sync primitives.
samber/cc-skills-golang@golang-concurrencySee Slice and Map Deep Dive for range pitfalls, subslice memory retention, and /.
slices.Clonemaps.CloneMap严禁并发访问——→ 如需了解同步原语,请查看。
samber/cc-skills-golang@golang-concurrency如需了解遍历陷阱、子切片内存保留以及/,请查看**切片与Map安全深度解析**。
slices.Clonemaps.CloneNumeric 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 // truego
// ✗ 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 // trueDivision by zero
除零错误
Integer division by zero panics. Float division by zero produces , , or .
+Inf-InfNaNgo
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 skill section.
samber/cc-skills-golang@golang-security整数除零会触发panic。浮点数除零会产生、或。
+Inf-InfNaNgo
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-securityResource Safety
资源安全
defer in loops — resource accumulation
循环中的defer——资源累积
defergo
// ✗ 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)
}defergo
// ✗ 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 for goroutine lifecycle and leak prevention.
samber/cc-skills-golang@golang-concurrency→ 如需了解Goroutine生命周期和泄漏预防,请查看。
samber/cc-skills-golang@golang-concurrencyImmutability & 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 is safe — prevents "forgot to initialize" bugs:
var x MyTypego
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 }设计类型时应保证是安全的——避免"忘记初始化"的bug:
var x MyTypego
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 for why init() should be avoided in favor of explicit constructors.
samber/cc-skills-golang@golang-design-patterns→ 如需了解为何应避免使用init()而改用显式构造函数,请查看。
samber/cc-skills-golang@golang-design-patternsEnforce with Linters
使用Linter强制执行
Many safety pitfalls are caught automatically by linters: , , , , . See the skill for configuration and usage.
errcheckforcetypeassertnilerrgovetstaticchecksamber/cc-skills-golang@golang-linter许多安全隐患可以被Linter自动捕获:、、、、。如需了解配置和使用方法,请查看技能。
errcheckforcetypeassertnilerrgovetstaticchecksamber/cc-skills-golang@golang-linterCross-References
交叉引用
- → See skill for concurrent access patterns and sync primitives
samber/cc-skills-golang@golang-concurrency - → See skill for slice/map internals, capacity growth, and container/ packages
samber/cc-skills-golang@golang-data-structures - → See skill for nil error interface trap
samber/cc-skills-golang@golang-error-handling - → See skill for security-relevant safety issues (memory safety, integer overflow)
samber/cc-skills-golang@golang-security - → See skill for debugging panics and race conditions
samber/cc-skills-golang@golang-troubleshooting
- → 如需了解并发访问模式和同步原语,请查看技能
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
常见错误
| Mistake | Fix |
|---|---|
Bare type assertion | Panics on type mismatch, crashing the program. Use |
| Returning typed nil in interface function | Interface holds (type, nil) which is != nil. Return untyped |
| Writing to a nil map | Nil maps have no backing storage — write panics. Initialize with |
Assuming | If capacity allows, both slices share the backing array. Use |
| |
| Values wrap silently (3B → -1.29B). Check against |
Comparing floats with | IEEE 754 representation is not exact ( |
| Integer division without zero check | Integer division by zero panics. Guard with |
| Returning internal slice/map reference | Callers can mutate your struct's internals through the shared backing array. Return a defensive copy |
Multiple | |
| Blocking forever on nil channel | Nil channels block on both send and receive. Always initialize before use |
| 错误 | 修复方案 |
|---|---|
裸类型断言 | 类型不匹配时会触发panic导致程序崩溃。使用 |
| 接口函数返回类型化nil | 接口存储(类型,nil),不等于nil。nil场景下返回非类型化 |
| 向nil map写入 | nil map没有底层存储——写入会触发panic。使用 |
假设 | 如果容量允许,两个切片会共享底层数组。使用 |
循环中的 | |
| 值会静默溢出(30亿→-12.94亿)。先与 |
使用 | IEEE 754表示法并非精确( |
| 整数除法不做零检查 | 整数除零会触发panic。除法前用 |
| 返回内部切片/Map引用 | 调用者可以通过共享底层数组修改结构体的内部数据。返回防御性拷贝 |
多个 | 跨文件的 |
| 在nil通道上永久阻塞 | nil通道在发送和接收时都会阻塞。使用前务必初始化 |
| ", |