go-clean-architecture
Use when scaffolding or refactoring a Go service into a framework-agnostic clean (hexagonal) architecture: Domain, Usecase,…
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,
Claude Code
$ npx -y skills add muratmirgun/gophers --skill go-generics --agent claude-code
Codex
$ npx skills add muratmirgun/gophers --skill go-generics -a codex -y
Cursor
$ npx skills add muratmirgun/gophers --skill go-generics -a cursor -y
Gemini CLI
$ npx skills add muratmirgun/gophers --skill go-generics -a gemini-cli -y
GitHub Copilot
$ npx skills add muratmirgun/gophers --skill go-generics -a github-copilot -y
OpenCode
$ npx skills add muratmirgun/gophers --skill go-generics -a opencode -y
Windsurf
$ npx skills add muratmirgun/gophers --skill go-generics -a windsurf -y
name: go-generics 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)." license: MIT compatibility: "Designed for Claude Code or similar AI coding agents. Generics require Go 1.18+; `cmp.Ordered` requires Go 1.21+." allowed-tools: Read Edit Write Glob Grep Bash(go:*) Bash(golangci-lint:*)
Generics 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.
1. **Write concrete first.** Reach for generics only when a second type actually needs the same logic. 2. **If an interface already models the behavior, use the interface.** Don't pile type parameters on top. 3. **Prefer standard constraints** (`comparable`, `cmp.Ordered`, `any`) over hand-rolled unions. 4. **Don't over-constrain.** `comparable` is usually enough; the narrower the constraint, the fewer callers benefit. 5. **Name type parameters with a single uppercase letter** (`T`, `K`, `V`, `E`) unless a longer name genuinely helps. 6. **Don't use generics for interface satisfaction.** `func F[T io.Reader](r T)` is just `func F(r io.Reader)`. 7. **Don't wrap stdlib containers** "for generic convenience" unless you eliminate real duplication.
Multiple types need the same logic? ├─ No → concrete type ├─ Yes → do they share a useful interface? │ ├─ Yes → use the interface │ └─ No → use generics
// Premature: only ever called with int
func Sum[T constraints.Integer | constraints.Float](xs []T) T {
var t T
for _, x := range xs { t += x }
return t
}
// Better
func SumInts(xs []int) int {
var t int
for _, x := range xs { t += x }
return t
}> "Write code, don't design types." — Griesemer & Taylor
| Name | Typical use | |---|---| | `T` | General element / first type | | `K` | Map key | | `V` | Map value | | `E` | Element of a collection | | `R` | Result of a transform |
Multi-letter names are reserved for constraints where the meaning is non-obvious:
func Marshal[Opts encoding.MarshalOptions](v any, opts Opts) ([]byte, error)
type Numeric interface {
~int | ~int8 | ~int16 | ~int32 | ~int64 |
~float32 | ~float64
}
func Sum[T Numeric](xs []T) T {
var t T
for _, x := range xs { t += x }
return t
}> Read [references/constraints.md](references/constraints.md) for the constraint catalogue, when `~` matters, and how type inference interacts with constraints.
// Adds complexity, eliminates no duplication
type Set[T comparable] struct {
m map[T]struct{}
}
// Use the builtin
seen := map[string]struct{}{}
seen["a"] = struct{}{}A 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`).
// Pointless type parameter
func Process[T io.Reader](r T) error { ... }
// Just use the interface
func Process(r io.Reader) error { ... }// Restrictive without reason
func Contains[T interface{ ~int | ~string }](xs []T, t T) bool { ... }
// comparable is enough
func Contains[T comparable](xs []T, t T) bool { ... }> Read [references/generics-vs-interfaces.md](references/generics-vs-interfaces.md) when interfaces and generics both seem to fit, and you have to choose.
type Old = pkg.New // alias: same type, alternate name type Old pkg.New // definition: new type, fresh method set
Type aliases (`=`) are for **package migrations** and gradual API moves. For new types, use a definition.
| Anti-pattern | Why it hurts | Do this instead | |---|---|---| | Generic for a single instantiation | Indirection without payoff | Concrete code | | Generic where an interface fits | Type parameter is just `io.Reader` in disguise | Accept the interface | | `interface{ ~int }` when `comparable` suffices | Restricts callers, no benefit | Loosen the constraint | | Custom `Numeric` constraint | `cmp.Ordered` exists | Standard constraint | | `Set[T]` wrapper around `map[T]struct{}` | Two-line struct, no methods | Use the map directly | | Generic function with two type params, neither used | The compiler can infer nothing | Drop one or both |
26 production-grade Go skills for Claude Code, Gemini CLI, and opencode. Battle-tested patterns from the Go community — codified as triggerable AI skills.
Use when scaffolding or refactoring a Go service into a framework-agnostic clean (hexagonal) architecture: Domain, Usecase,…
Invoke this skill to systematically review a Go change against community style standards before merging. Walks the diff topic by…
Use when writing or reviewing Go code for clarity, formatting, control flow, variable declarations, switch usage, and function…
Use when writing or reviewing concurrent Go code — goroutines, channels, select, mutexes, atomics, errgroup, singleflight, worker…
Use when designing, propagating, or debugging context.Context flow in Go — first-parameter placement, deadlines and cancellation,…
Use when writing conditionals, loops, switches, type switches, or blank-identifier patterns in Go. Covers if-with-initialization,…
You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…
Ask which skill or flow fits your situation. A router over the skills in this repo.