[teamai] Push 87 resource(s) from XingfenD

This commit is contained in:
2026-09-10 16:10:45 +08:00
parent 425c9c078a
commit 65c04def51
1314 changed files with 211681 additions and 0 deletions
@@ -0,0 +1,197 @@
# Nil Safety Deep Dive
## Nil Pointer Receivers
MUST check for nil before calling methods on pointer receivers from external sources. A method call on a nil pointer does not always panic — it depends on whether the method dereferences the receiver:
```go
type Logger struct {
prefix string
}
// ✓ Safe on nil but NEVER do that — does not dereference l
func (l *Logger) IsEnabled() bool {
return l != nil
}
// ✗ Panics on nil — dereferences l to access prefix
func (l *Logger) Log(msg string) {
fmt.Printf("[%s] %s\n", l.prefix, msg)
}
var l *Logger
l.IsEnabled() // false — works fine
l.Log("test") // panic: nil pointer dereference
```
Anyway, NEVER call a method on a nil pointer.
### Designing nil-safe receivers
When a nil receiver is a valid state (e.g., optional components), guard against it explicitly:
```go
func (l *Logger) Log(msg string) {
if l == nil {
return // silently skip if no logger configured
}
fmt.Printf("[%s] %s\n", l.prefix, msg)
}
```
This pattern is useful for optional dependencies, but use it sparingly — a nil receiver usually signals a bug, not an intentional state. Document when nil is an expected value.
## Nil Function Values
NEVER rely on nil function values — always validate before calling. Calling a nil `func` variable panics:
```go
// ✗ Bad — panics if callback was never set
type Worker struct {
onComplete func(result string)
}
func (w *Worker) Finish(result string) {
w.onComplete(result) // panic if onComplete is nil
}
// ✓ Good — check before calling
func (w *Worker) Finish(result string) {
if w.onComplete != nil {
w.onComplete(result)
}
}
```
### Default function pattern
Provide a no-op default to avoid nil checks at every call site:
```go
func NewWorker(opts ...Option) *Worker {
w := &Worker{
onComplete: func(string) {}, // no-op default
}
for _, opt := range opts {
opt(w)
}
return w
}
```
## Nil and Error Comparisons
### Returning nil error correctly
Interface comparisons with nil MUST account for the nil interface trap. A function returning `error` must return the untyped `nil`, not a typed nil pointer:
```go
// ✗ Bad — returns non-nil error interface
func validate(s string) error {
var err *ValidationError // typed nil
if s == "" {
err = &ValidationError{Field: "name"}
}
return err // even when err is nil, interface is non-nil
}
// ✓ Good — return nil explicitly
func validate(s string) error {
if s == "" {
return &ValidationError{Field: "name"}
}
return nil
}
```
### Checking error chains with nil
`errors.Is(err, nil)` returns `true` only if `err` is truly nil. It does not help with the nil interface trap — the trap occurs before the error reaches `errors.Is`.
## Nil in Generic Code
### The `comparable` constraint and nil
Generic code MUST handle the zero value of type parameters correctly. Type parameters constrained by `comparable` can be compared with `==`, but nil is not always a valid value:
```go
// ✗ Confusing — T may or may not be nillable
func IsZero[T comparable](v T) bool {
var zero T
return v == zero // works, but "zero" for *Foo is nil, for int is 0
}
// ✓ Better — be explicit about what "empty" means
func IsNil[T interface{ ~*U }, U any](v T) bool {
return v == nil
}
```
### Nil checks with unconstrained type parameters
You cannot compare an unconstrained type parameter to nil:
```go
// ✗ Does not compile
func Check[T any](v T) bool {
return v == nil // compile error: cannot compare T with nil
}
// ✓ Good — use reflect or constrain to pointer types
func IsNilPtr[T any](v *T) bool {
return v == nil
}
```
## Patterns for Nil-Safe APIs
### Constructor with defaults
Require initialization through a constructor, making the zero value impossible for external callers:
```go
type Client struct {
httpClient *http.Client
baseURL string
}
// Constructor guarantees non-nil fields
func NewClient(baseURL string) *Client {
return &Client{
httpClient: http.DefaultClient,
baseURL: baseURL,
}
}
```
### Lazy initialization for zero-value usability
When you want the zero value to be usable but need internal resources:
```go
type Cache struct {
mu sync.Mutex
data map[string]any
}
func (c *Cache) Get(key string) (any, bool) {
c.mu.Lock()
defer c.mu.Unlock()
if c.data == nil {
return nil, false
}
v, ok := c.data[key]
return v, ok
}
func (c *Cache) Set(key string, val any) {
c.mu.Lock()
defer c.mu.Unlock()
if c.data == nil {
c.data = make(map[string]any)
}
c.data[key] = val
}
```
→ See `samber/cc-skills-golang@golang-error-handling` skill for nil error comparison pitfalls.
@@ -0,0 +1,194 @@
# Slice and Map Safety Deep Dive
## Range Loop Variable Capture
### Pre-Go 1.22: shared loop variable
NEVER store pointers to loop variables in Go < 1.22 — capture by value. Before Go 1.22, the range loop variable was reused across iterations. Capturing it in a closure or storing its address caused all references to point to the final value:
```go
// ✗ Bad (pre-1.22) — all goroutines see the last value of v
var funcs []func()
for _, v := range []string{"a", "b", "c"} {
funcs = append(funcs, func() { fmt.Println(v) })
}
for _, f := range funcs {
f() // prints "c", "c", "c"
}
// ✓ Fix (pre-1.22) — shadow the variable
for _, v := range []string{"a", "b", "c"} {
v := v // re-declare v in inner scope
funcs = append(funcs, func() { fmt.Println(v) })
}
```
### Go 1.22+: per-iteration scoping
Go 1.22 changed loop variable semantics — each iteration creates a new variable. The closure bug no longer occurs. However, if your module targets `go 1.21` or earlier in `go.mod`, the old behavior applies. Check your `go.mod` version.
## Storing Pointer to Loop Variable
The same pre-1.22 issue applies to storing `&v`:
```go
// ✗ Bad (pre-1.22) — all pointers point to the same address
type Item struct{ Name string }
items := []Item{{Name: "a"}, {Name: "b"}}
var ptrs []*Item
for _, item := range items {
ptrs = append(ptrs, &item) // all point to same loop variable
}
// ptrs[0].Name == "b", ptrs[1].Name == "b"
// ✓ Good — take address of the slice element directly
for i := range items {
ptrs = append(ptrs, &items[i])
}
```
In Go 1.22+, `&item` is safe because each iteration has its own `item`. But taking `&items[i]` is still clearer and avoids a copy.
## Slice Header vs Backing Array
A slice is a 3-word struct: `{pointer, length, capacity}`. Multiple slices can share the same backing array:
```
a := make([]int, 3, 5)
┌─────┬─────┬─────┐
│ ptr │ len=3│cap=5│ ← slice header for a
└──┬──┴─────┴─────┘
│
▼
┌───┬───┬───┬───┬───┐
│ 0 │ 0 │ 0 │ │ │ ← backing array (5 elements)
└───┴───┴───┴───┴───┘
b := a[1:2]
┌─────┬─────┬─────┐
│ ptr │ len=1│cap=4│ ← slice header for b (shares backing array)
└──┬──┴─────┴─────┘
│ (points to a[1])
```
This is why `append(a, x)` can affect `b` if `a` has spare capacity. Use the full slice expression `a[:len(a):len(a)]` to set cap == len and force a new allocation on append.
## Subslice Retains Full Backing Array
Subslice retention: MUST use `slices.Clone` or `copy` when keeping a small slice from a large backing array. Slicing a large slice for a small piece prevents GC of the entire backing array:
```go
// ✗ Bad — small keeps the entire 1MB array alive
func getHeader(data []byte) []byte {
return data[:64] // shares backing array with data
}
// ✓ Good — copy to release the large array
func getHeader(data []byte) []byte {
header := make([]byte, 64)
copy(header, data[:64])
return header
}
// ✓ Good (Go 1.21+) — use slices.Clone
import "slices"
func getHeader(data []byte) []byte {
return slices.Clone(data[:64])
}
```
## Standard Library Clone Helpers (Go 1.21+)
```go
import (
"maps"
"slices"
)
// Shallow copy a slice
clone := slices.Clone(original)
// Shallow copy a map
clone := maps.Clone(original)
```
These are the preferred way to make defensive copies. They are clearer than manual `make` + `copy` and handle nil inputs correctly (returning nil, not an empty collection).
## Map Iteration Order
Map iteration order MUST NOT be depended upon — it is randomized by the runtime:
```go
// ✗ Bad — output order changes between runs
m := map[string]int{"a": 1, "b": 2, "c": 3}
for k, v := range m {
fmt.Printf("%s=%d ", k, v) // could be "b=2 a=1 c=3" or any permutation
}
// ✓ Good (Go 1.23+) — sort keys when order matters
keys := slices.Sorted(maps.Keys(m))
for _, k := range keys {
fmt.Printf("%s=%d ", k, m[k])
}
```
## Deleting During Iteration
### Maps — safe
Deleting map entries during `range` is explicitly safe in Go:
```go
// ✓ Safe — defined behavior
for k, v := range m {
if shouldDelete(v) {
delete(m, k) // safe during range
}
}
```
### Slices — needs care
Deleting from a slice during iteration requires index management:
```go
// ✗ Bad — skips elements after deletion
for i, v := range items {
if shouldDelete(v) {
items = append(items[:i], items[i+1:]...) // shifts elements, next iteration skips one
}
}
// ✓ Good — iterate backwards
for i := len(items) - 1; i >= 0; i-- {
if shouldDelete(items[i]) {
items = append(items[:i], items[i+1:]...)
}
}
// ✓ Good (Go 1.21+) — use slices.DeleteFunc
items = slices.DeleteFunc(items, shouldDelete)
```
## Comparing Slices and Maps
Slice/map comparison MUST use `slices.Equal`/`maps.Equal` (Go 1.21+), NEVER `==` (which doesn't compile for slices). Use standard library helpers:
```go
import (
"maps"
"slices"
)
// ✓ Good (Go 1.21+)
slices.Equal(a, b) // element-wise comparison
maps.Equal(m1, m2) // key-value comparison
// For custom comparison
slices.EqualFunc(a, b, func(x, y Item) bool {
return x.ID == y.ID
})
```
→ See `samber/cc-skills-golang@golang-modernize` skill for Go 1.22+ loop variable semantics.