# Go rules

Follow these CodeWiki-derived rules when you work in this project.

- `T ~int` means that `T` has underlying type `int`; it doesn't mean values in the body have already been converted to the predeclared type.
  Why: A result of type `T` retains a static type such as `UserID` or `Score`.
  Source: [Constraints and type sets](https://codewiki.com/go/type-parameters/)
- `comparable` proves that `==` is legal in the generic body, but it doesn't guarantee that every dynamic value inside an interface argument is strictly comparable.
  Why: A slice, map, or function inside `any` can still panic during equality or map insertion.
  Source: [Constraints and type sets](https://codewiki.com/go/type-parameters/)
- A call to `func ZeroT any T` has no ordinary arguments.
  Why: In `value := Zero()`, the compiler has insufficient evidence to determine `T`, so the call is invalid.
  Source: [Constraints and type sets](https://codewiki.com/go/type-parameters/)
- The `T` in a `Stack[T]` receiver corresponds to an existing base-type parameter.
  Why: A Go 1.27 method's `Map[U any]` is an additional parameter owned by that method. Their scopes and purposes differ, and interface methods can't declare the latter kind.
  Source: [Constraints and type sets](https://codewiki.com/go/type-parameters/)
- Fields of `var store Store[K, V]` take zero values based on the actual type arguments.
  Why: A slice field usually accepts append immediately, but a nil map field panics on its first write; a returned zero `V` may also be valid data.
  Source: [Constraints and type sets](https://codewiki.com/go/type-parameters/)
- Do not assume this is safe: deferring each resource close directly inside a long loop retains all resources until the outer function ends, not until the current iteration ends.
  Source: [defer, panic and recover](https://codewiki.com/go/defer-panic-recover/)
- Do not assume this is safe: `log.Fatal` ultimately calls `os.Exit(1)`, and `os.Exit` terminates the process immediately without running deferred calls.
  Source: [defer, panic and recover](https://codewiki.com/go/defer-panic-recover/)
- A broad `recover` around a large section of business logic disguises nil dereferences, bounds errors, and broken invariants as ordinary failures, after which the process may keep using corrupt state.
  Source: [defer, panic and recover](https://codewiki.com/go/defer-panic-recover/)
- A `recover` deferred in a parent goroutine cannot catch a child panic, and a deferred function cannot delegate `recover` to an ordinary helper function.
  Source: [defer, panic and recover](https://codewiki.com/go/defer-panic-recover/)
- Ignoring errors from `Close`, `Flush`, or transaction commit can report unpersisted data as success; always replacing the earlier error instead loses the original failure.
  Source: [defer, panic and recover](https://codewiki.com/go/defer-panic-recover/)
- Do not assume this is safe: declaring a nil function variable and then writing `defer cleanup()` does not fail at registration; it panics when the nil function is invoked during exit.
  Source: [defer, panic and recover](https://codewiki.com/go/defer-panic-recover/)
- Assigning an error to `_`, or overwriting it before checking, converts an explicit failure into bad data or a misleading later error.
  Source: [Error handling](https://codewiki.com/go/error-handling/)
- Do not assume this is safe: code that compares `err.Error()` or searches it for a substring depends on prose that may change between package versions, operating systems, or wrapper layers.
  Source: [Error handling](https://codewiki.com/go/error-handling/)
- Do not assume this is safe: wrapping every dependency error with `%w` can accidentally make that dependency's sentinels and types part of your public API.
  Source: [Error handling](https://codewiki.com/go/error-handling/)
- An `error` interface is non-nil when it contains a concrete type even if the concrete pointer stored inside it is nil.
  Source: [Error handling](https://codewiki.com/go/error-handling/)
- Do not assume this is safe: a function that logs an error and returns it asks every caller to report the same event again, often without a shared request identifier or redaction policy.
  Source: [Error handling](https://codewiki.com/go/error-handling/)
- Generated and hand-written helpers sometimes panic for invalid input, missing files, or network failures that their callers could handle normally.
  Source: [Error handling](https://codewiki.com/go/error-handling/)
- `fmt.Errorf("read config: %v", err)` retains the visible message but creates no wrapping relationship, so later calls to `errors.Is` and `errors.As` cannot see `err`.
  Source: [Error wrapping](https://codewiki.com/go/error-wrapping/)
- `err == target` and `err.(*Type)` inspect only the outer error, so adding any contextual wrapper can break them.
  Source: [Error wrapping](https://codewiki.com/go/error-wrapping/)
- Do not assume this is safe: `fmt.Errorf` does not return nil merely because the error supplied to `%w` is nil; unconditional wrapping turns a successful path into a non-nil error.
  Source: [Error wrapping](https://codewiki.com/go/error-wrapping/)
- Do not assume this is safe: `errors.As` panics when its target is nil or is not a non-nil pointer to an error type or interface; pointer-receiver error types make it easy to add or omit one pointer level.
  Source: [Error wrapping](https://codewiki.com/go/error-wrapping/)
- Once you wrap `sql.ErrNoRows` or a concrete vendor SDK type, callers may treat that matching behavior as your API even if you meant only to retain diagnostic detail.
  Source: [Error wrapping](https://codewiki.com/go/error-wrapping/)
- Several `%w` verbs, `errors.Join`, and a custom `Unwrap() []error` all create branches; repeatedly calling `errors.Unwrap` misses them and cannot represent the full tree reliably.
  Source: [Error wrapping](https://codewiki.com/go/error-wrapping/)
- `comparable` guarantees only `==` and `!=`.
  Why: Generated code declaring `func MinT comparable` and then writing `a < b` in the body won't compile.
  Source: [Generics](https://codewiki.com/go/generics/)
- `[T any]` doesn't make `+`, field selection, indexing, or method calls available.
  Why: The body can perform only operations guaranteed across the constraint's whole type set.
  Source: [Generics](https://codewiki.com/go/generics/)
- The constraint `int | string` includes only those two predeclared types.
  Why: `type UserID int` doesn't satisfy it merely because it can be converted to `int`.
  Source: [Generics](https://codewiki.com/go/generics/)
- Go methods can't declare new type parameters beyond the receiver's parameters.
  Why: `func (s Stack[T]) MapU any` is a syntax error.
  Source: [Generics](https://codewiki.com/go/generics/)
- A `First`, `Min`, or `Pop` returning only `T` can't distinguish failure from a valid zero value.
  Why: The value is `""` for strings, `nil` for pointers, and a struct with every field zeroed for structs.
  Source: [Generics](https://codewiki.com/go/generics/)
- Using `:=` in an inner block can create a same-named variable.
  Why: The outer variable keeps its old value while the code still compiles.
  Source: [Go fundamentals](https://codewiki.com/go/fundamentals/)
- `text[index]` returns a byte and `len(text)` returns a byte count, so slicing UTF-8 text with those results can cut through an encoded code point.
  Source: [Go fundamentals](https://codewiki.com/go/fundamentals/)
- Do not assume this is safe: writing `T(value)` requests a conversion; it does not promise to preserve the numeric range, precision, or original meaning.
  Source: [Go fundamentals](https://codewiki.com/go/fundamentals/)
- The blank identifier `_` can satisfy the compiler's requirement to use local values, but `value, _ := strconv.Atoi(raw)` silently turns invalid input into a zero-valued result.
  Source: [Go fundamentals](https://codewiki.com/go/fundamentals/)
- Code translated from C, Java, or JavaScript may rely on cases falling through automatically, but Go exits the `switch` after the matching branch.
  Source: [Go fundamentals](https://codewiki.com/go/fundamentals/)
- A value being able to call a pointer-receiver method doesn't mean the value type implements an interface containing that method.
  Source: [Interfaces](https://codewiki.com/go/interfaces/)
- Returning a nil concrete error pointer produces an `error` interface that isn't `nil`.
  Source: [Interfaces](https://codewiki.com/go/interfaces/)
- A single-result type assertion turns normal input variation into a panic.
  Source: [Interfaces](https://codewiki.com/go/interfaces/)
- Do not assume this is safe: defining a large interface beside its implementation makes every consumer depend on methods it doesn't need.
  Source: [Interfaces](https://codewiki.com/go/interfaces/)
- Two interface values being comparable doesn't make every comparison safe.
  Source: [Interfaces](https://codewiki.com/go/interfaces/)
- Do not assume this is safe: a struct's map field may look fine on reads when it was never initialized, then panic on the first assignment.
  Why: Tests that cover only read paths easily miss this.
  Source: [Maps](https://codewiki.com/go/maps/)
- `if counts[key] == 0` cannot tell an absent key from a present key whose value is zero.
  Why: Booleans, strings, pointers, and interface values have the same ambiguity.
  Source: [Maps](https://codewiki.com/go/maps/)
- Map iteration order is unspecified.
  Why: Stable-looking output from one local run does not turn that order into a language guarantee.
  Source: [Maps](https://codewiki.com/go/maps/)
- Do not assume this is safe: "Never delete while ranging" is not a Go rule.
  Why: Removing an unreached entry prevents it from appearing; adding entries is the operation with unspecified visibility in that loop.
  Source: [Maps](https://codewiki.com/go/maps/)
- Do not assume this is safe: `profiles[id].Name = "Lin"` does not compile because a map index expression is not an addressable struct variable.
  Why: Slice index expressions have different addressability rules.
  Source: [Maps](https://codewiki.com/go/maps/)
- `backup := source` copies only the map value, so both variables share entries.
  Why: Even `maps.Clone` does not recursively copy nested slices, maps, or pointers.
  Source: [Maps](https://codewiki.com/go/maps/)
- When one goroutine writes a map, a potentially overlapping read, write, delete, or iteration in another goroutine creates a data race.
  Why: A program that does not fail immediately is still incorrect.
  Source: [Maps](https://codewiki.com/go/maps/)
- Generated or refactored code gives a mutating method a value receiver.
  Why: The method returns normally but changes only a copy; if fields contain slices or maps, some mutations may still leak into shared storage, making the behavior even less consistent.
  Source: [Methods](https://codewiki.com/go/methods/)
- Do not assume this is safe: after `x.M()` compiles, code assumes that `T` satisfies an interface requiring `M`.
  Why: If `M` has a `*T` receiver, addressability helped only that call by taking an address; the method set of `T` didn't change.
  Source: [Methods](https://codewiki.com/go/methods/)
- Do not assume this is safe: a value receiver on a type containing `sync.Mutex` or another field that must not be copied after first use copies the lock on every call.
  Why: Locking the copy doesn't protect the original object, and `go vet` commonly reports such receivers or by-value transfers as `copylocks`.
  Source: [Methods](https://codewiki.com/go/methods/)
- A pointer-receiver method can receive `nil`, but that doesn't make the call safe.
  Why: Dereferencing a field in the body panics; calling a value-receiver method through a nil `*T` can panic on the implicit dereference before the body starts.
  Source: [Methods](https://codewiki.com/go/methods/)
- Do not assume this is safe: after an embedded type's method is promoted, generated code assumes the outer type is a subtype or that an inner method dynamically calls an outer “override.” Promotion is only a selector and method-set rule; it doesn't provide that dynamic dispatch.
  Source: [Methods](https://codewiki.com/go/methods/)
- Calling `Type()`, `Elem()`, or `Interface()` before checking validity.
  Why: `ValueOf(nil)`, a failed `FieldByName`, and `Elem()` on a nil pointer can all produce an invalid `Value`.
  Source: [reflect.Type and reflect.Value](https://codewiki.com/go/reflection/)
- Do not treat `CanAddr()` as `CanSet()`, or assuming same-package code; doing so can modify an unexported field through reflection.
  Why: An addressable value can still be restricted by visibility, and a direct `Set` panics.
  Source: [reflect.Type and reflect.Value](https://codewiki.com/go/reflection/)
- Comparing only `Kind` and then using `Convert` unconditionally.
  Why: Named `Int64` types can carry different domain meanings, and a numeric conversion can also change the value.
  Source: [reflect.Type and reflect.Value](https://codewiki.com/go/reflection/)
- Looking up a pointer-receiver-only method on a value receiver.
  Why: `MethodByName` returns an invalid `Value`, and calling it directly then panics.
  Source: [reflect.Type and reflect.Value](https://codewiki.com/go/reflection/)
- Passing a request parameter directly to `FieldByName` or `MethodByName`.
  Why: This turns every reachable exported field or method into an unreviewed external operation surface.
  Source: [reflect.Type and reflect.Value](https://codewiki.com/go/reflection/)
- Repeating field discovery, tag parsing, and method lookup in a loop whose type is already fixed.
  Why: This obscures business intent and repeats the same metadata work.
  Source: [reflect.Type and reflect.Value](https://codewiki.com/go/reflection/)
- Calling `Type()`, `Elem()`, or `Interface()` before checking validity.
  Why: `ValueOf(nil)`, a failed `FieldByName`, and `Elem()` on a nil pointer can all produce an invalid `Value`. Fix: Check `IsValid()` immediately after every API that may return an invalid value; call `IsNil()` or `Elem()` only after confirming an applicable `Kind`.
  Source: [Reflection](https://codewiki.com/go/reflect/)
- Do not treat `CanAddr()` as `CanSet()`, or assuming code in the same package; doing so can reflectively modify an unexported field.
  Why: An addressable value may still be restricted by visibility, and a direct `Set` will panic. Fix: Check `CanSet()` before writing and `CanInterface()` before extracting `any`; expose mutable state through exported fields, constructors, or methods.
  Source: [Reflection](https://codewiki.com/go/reflect/)
- Comparing only `Kind`, then using `Convert` unconditionally.
  Why: Named types with the same `Int64` kind can carry different domain meanings, and numeric conversion can truncate; `ConvertibleTo` proves that Go permits a conversion, not that the data is valid. Fix: Require `AssignableTo` by default; when conversion is necessary, list accepted source types and validate ranges and business rules first.
  Source: [Reflection](https://codewiki.com/go/reflect/)
- Looking for a pointer-only method on a value receiver.
  Why: `MethodByName` returns an invalid `Value`, and calling it directly afterward panics. Fix: State whether callers must provide `T` or `*T`, check the result with `IsValid()`, then validate the complete function signature and result count.
  Source: [Reflection](https://codewiki.com/go/reflect/)
- Passing a request parameter directly to `FieldByName` or `MethodByName`.
  Why: This turns every reachable exported field or method into an unreviewed external operation, enabling mass assignment or unauthorized calls. Fix: Map external names to allowed field indexes or methods with a fixed table, and finish authentication, authorization, and input validation before entering reflection code.
  Source: [Reflection](https://codewiki.com/go/reflect/)
- Repeating field discovery, tag parsing, and method lookup in a loop whose types are already stable.
  Why: The code becomes harder to read and repeats the same metadata work. Fix: First check whether ordinary code, an interface, or generics express the need more directly; when reflection is necessary, cache an immutable plan keyed by `reflect.Type` and benchmark the real workload before optimizing.
  Source: [Reflection](https://codewiki.com/go/reflect/)
- `backup := records` copies only the slice header.
  Why: Changing `backup[i]` will usually change `records[i]` too.
  Source: [Slices](https://codewiki.com/go/slices/)
- With spare capacity, `append` can overwrite a shared array.
  Why: Without it, the result moves away from the original array. Depending on either path creates capacity-sensitive bugs.
  Source: [Slices](https://codewiki.com/go/slices/)
- `make([]Item, n)` already contains `n` zero-valued elements.
  Why: Appending another `n` results creates length `2n` with a zero-valued first half.
  Source: [Slices](https://codewiki.com/go/slices/)
- Returning a tiny subslice of a large buffer can keep the whole backing array reachable.
  Why: Reducing capacity to length does not detach that storage.
  Source: [Slices](https://codewiki.com/go/slices/)
- Replacing a nil result with an empty literal may leave loops unchanged while changing JSON from `null` to `[]`, breaking an external schema.
  Source: [Slices](https://codewiki.com/go/slices/)
- Concurrent writes by several goroutines to one slice variable or shared elements cause a data race.
  Why: Reserving enough capacity does not make `append` safe.
  Source: [Slices](https://codewiki.com/go/slices/)
- Do not assume this is safe: `library.Record{1, "ready"}` depends on field count, order, and visibility.
  Why: Adding or reordering fields can stop the code from compiling; swapping same-typed fields can preserve compilation while changing meaning.
  Source: [Structs](https://codewiki.com/go/structs/)
- `snapshot := state` copies the outer struct, but mutable data in slice, map, pointer, and interface fields may remain shared.
  Why: A change through one copy can appear through the other.
  Source: [Structs](https://codewiki.com/go/structs/)
- `func (account Account) Rename(...)` changes a receiver copy.
  Why: The method can return normally while the caller's `account` stays unchanged; if the struct contains a lock, the receiver also copies that lock.
  Source: [Structs](https://codewiki.com/go/structs/)
- Do not assume this is safe: embedding `Contact` does not make `Customer` a `Contact`.
  Why: A promoted selector can be shadowed by an outer declaration or become ambiguous when several candidates occur at the same depth.
  Source: [Structs](https://codewiki.com/go/structs/)
- Adding a slice or map field makes a previously comparable struct unusable with `==` or as a map key.
  Why: An `any` field can let comparison compile and still panic when it holds a non-comparable dynamic value.
  Source: [Structs](https://codewiki.com/go/structs/)
- A tag has no behavior by itself.
  Why: `validate:"required"` matters only when its library reads and executes it, and `json:"-"` constrains only encoders that honor that key.
  Source: [Structs](https://codewiki.com/go/structs/)
