← GophersCONTENT HISTORYWHAT CHANGED · RULE-BASED ANALYSIS
Update to Gophers
Snapshot Sep 30, 2026 · 23:14 UTC · version 0.1.0
Collection source: not recorded for this historical snapshot.
First saved snapshot
No earlier snapshot is available to establish a change.
Compare saved observations
Download comparison JSONFull technical diff · 0 changed fields
Full snapshot data
{
"description": "Use when deciding whether to introduce Go generics, writing generic functions or types, composing type constraints, or choosing between type aliases and type definitions. Apply proactively when a user is writing a utility function that could conceivably work with multiple types, even if they didn't mention generics. Does not cover interface-only designs (see go-interfaces).",
"included_files": [
{
"relative_path": "agents/openai.yaml",
"size_in_bytes": 224
},
{
"relative_path": "references/constraints.md",
"size_in_bytes": 2583
},
{
"relative_path": "references/generics-vs-interfaces.md",
"size_in_bytes": 2279
}
],
"name": "go-generics",
"skill_md_contents": "---\nname: go-generics\ndescription: \"Use when deciding whether to introduce Go generics, writing generic functions or types, composing type constraints, or choosing between type aliases and type definitions. Apply proactively when a user is writing a utility function that could conceivably work with multiple types, even if they didn't mention generics. Does not cover interface-only designs (see go-interfaces).\"\nlicense: MIT\ncompatibility: \"Designed for Claude Code or similar AI coding agents. Generics require Go 1.18+; `cmp.Ordered` requires Go 1.21+.\"\nallowed-tools: Read Edit Write Glob Grep Bash(go:*) Bash(golangci-lint:*)\n---\n\n# Go Generics\n\nGenerics are a powerful but easy-to-misuse feature. The Go answer is pragmatic: write concrete code first, then generalize only when you have a real second caller.\n\n## Core Rules\n\n1. **Write concrete first.** Reach for generics only when a second type actually needs the same logic.\n2. **If an interface already models the behavior, use the interface.** Don't pile type parameters on top.\n3. **Prefer standard constraints** (`comparable`, `cmp.Ordered`, `any`) over hand-rolled unions.\n4. **Don't over-constrain.** `comparable` is usually enough; the narrower the constraint, the fewer callers benefit.\n5. **Name type parameters with a single uppercase letter** (`T`, `K`, `V`, `E`) unless a longer name genuinely helps.\n6. **Don't use generics for interface satisfaction.** `func F[T io.Reader](r T)` is just `func F(r io.Reader)`.\n7. **Don't wrap stdlib containers** \"for generic convenience\" unless you eliminate real duplication.\n\n## Decision Flow\n\n```\nMultiple types need the same logic?\n├─ No → concrete type\n├─ Yes → do they share a useful interface?\n│ ├─ Yes → use the interface\n│ └─ No → use generics\n```\n\n## When NOT to Use Generics\n\n```go\n// Premature: only ever called with int\nfunc Sum[T constraints.Integer | constraints.Float](xs []T) T {\n var t T\n for _, x := range xs { t += x }\n return t\n}\n\n// Better\nfunc SumInts(xs []int) int {\n var t int\n for _, x := range xs { t += x }\n return t\n}\n```\n\n> \"Write code, don't design types.\" — Griesemer & Taylor\n\n## When Generics Pay Off\n\n- A library function the standard library would have written generically: `slices.Index`, `maps.Keys`, `slices.SortFunc`.\n- Concurrent-safe data structures (typed sets, ordered maps) where boxing into `any` would be both ugly and slow.\n- Map/Reduce-style helpers that genuinely apply to many element types.\n\n## Type Parameter Naming\n\n| Name | Typical use |\n|---|---|\n| `T` | General element / first type |\n| `K` | Map key |\n| `V` | Map value |\n| `E` | Element of a collection |\n| `R` | Result of a transform |\n\nMulti-letter names are reserved for constraints where the meaning is non-obvious:\n\n```go\nfunc Marshal[Opts encoding.MarshalOptions](v any, opts Opts) ([]byte, error)\n```\n\n## Constraint Composition\n\n```go\ntype Numeric interface {\n ~int | ~int8 | ~int16 | ~int32 | ~int64 |\n ~float32 | ~float64\n}\n\nfunc Sum[T Numeric](xs []T) T {\n var t T\n for _, x := range xs { t += x }\n return t\n}\n```\n\n- `~int` means \"anything whose underlying type is `int`\" — covers `type Celsius int`.\n- `|` unions widen the set.\n- Prefer `cmp.Ordered` (Go 1.21+) over rolling your own.\n\n> Read [references/constraints.md](references/constraints.md) for the constraint catalogue, when `~` matters, and how type inference interacts with constraints.\n\n## Common Pitfalls\n\n### Don't Wrap Stdlib Types Generically\n\n```go\n// Adds complexity, eliminates no duplication\ntype Set[T comparable] struct {\n m map[T]struct{}\n}\n\n// Use the builtin\nseen := map[string]struct{}{}\nseen[\"a\"] = struct{}{}\n```\n\nA generic wrapper around `map[T]struct{}` is only worth it if you keep it for many call sites *and* provide methods that pay for the indirection (e.g., `Union`, `Intersect`).\n\n### Don't Use Generics for Interface Satisfaction\n\n```go\n// Pointless type parameter\nfunc Process[T io.Reader](r T) error { ... }\n\n// Just use the interface\nfunc Process(r io.Reader) error { ... }\n```\n\n### Don't Over-Constrain\n\n```go\n// Restrictive without reason\nfunc Contains[T interface{ ~int | ~string }](xs []T, t T) bool { ... }\n\n// comparable is enough\nfunc Contains[T comparable](xs []T, t T) bool { ... }\n```\n\n> Read [references/generics-vs-interfaces.md](references/generics-vs-interfaces.md) when interfaces and generics both seem to fit, and you have to choose.\n\n## Type Aliases vs Definitions\n\n```go\ntype Old = pkg.New // alias: same type, alternate name\ntype Old pkg.New // definition: new type, fresh method set\n```\n\nType aliases (`=`) are for **package migrations** and gradual API moves. For new types, use a definition.\n\n## Anti-Patterns\n\n| Anti-pattern | Why it hurts | Do this instead |\n|---|---|---|\n| Generic for a single instantiation | Indirection without payoff | Concrete code |\n| Generic where an interface fits | Type parameter is just `io.Reader` in disguise | Accept the interface |\n| `interface{ ~int }` when `comparable` suffices | Restricts callers, no benefit | Loosen the constraint |\n| Custom `Numeric` constraint | `cmp.Ordered` exists | Standard constraint |\n| `Set[T]` wrapper around `map[T]struct{}` | Two-line struct, no methods | Use the map directly |\n| Generic function with two type params, neither used | The compiler can infer nothing | Drop one or both |\n\n## Verification Checklist\n\n- [ ] At least two real, current call sites benefit from the type parameter\n- [ ] An interface would not be a simpler model\n- [ ] Constraint is the loosest one that compiles (`any`, `comparable`, `cmp.Ordered` preferred)\n- [ ] Type parameter names are conventional letters unless clarity demands more\n- [ ] No `T` exists only to satisfy an interface — accept the interface instead\n- [ ] No generic wrapper added without methods that justify it\n- [ ] Doc comment explains what the type parameter must support\n\n## References\n\n- [references/constraints.md](references/constraints.md) — constraint catalogue, `~` and `|`, `cmp.Ordered`, type inference\n- [references/generics-vs-interfaces.md](references/generics-vs-interfaces.md) — picking between a generic and an interface\n"
}SHA-256 of public snapshot: 3aa551a55a6353bf203f2875a9e64ee9284002a7d3451d8c17b453ba92b59bd7