Go generics
26 production-grade Go skills for Claude Code, Gemini CLI, and opencode.
npx -y skills add muratmirgun/gophers --skill go-genericsAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 8 stars8 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
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).
The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.
SKILL.md
6.3 KB, as published. Nobody here has run it
Go Generics
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.
Core Rules
- Write concrete first. Reach for generics only when a second type actually needs the same logic.
- If an interface already models the behavior, use the interface. Don't pile type parameters on top.
- Prefer standard constraints (
comparable,cmp.Ordered,any) over hand-rolled unions. - Don't over-constrain.
comparableis usually enough; the narrower the constraint, the fewer callers benefit. - Name type parameters with a single uppercase letter (
T,K,V,E) unless a longer name genuinely helps. - Don't use generics for interface satisfaction.
func F[T io.Reader](r T)is justfunc F(r io.Reader). - Don't wrap stdlib containers "for generic convenience" unless you eliminate real duplication.
Decision Flow
Multiple types need the same logic?
├─ No → concrete type
├─ Yes → do they share a useful interface?
│ ├─ Yes → use the interface
│ └─ No → use generics
When NOT to 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
When Generics Pay Off
- A library function the standard library would have written generically:
slices.Index,maps.Keys,slices.SortFunc. - Concurrent-safe data structures (typed sets, ordered maps) where boxing into
anywould be both ugly and slow. - Map/Reduce-style helpers that genuinely apply to many element types.
Type Parameter Naming
| 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)
Constraint Composition
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
}
~intmeans "anything whose underlying type isint" — coverstype Celsius int.|unions widen the set.- Prefer
cmp.Ordered(Go 1.21+) over rolling your own.
Read references/constraints.md for the constraint catalogue, when
~matters, and how type inference interacts with constraints.
Common Pitfalls
Don't Wrap Stdlib Types Generically
// 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).
Don't Use Generics for Interface Satisfaction
// Pointless type parameter
func Process[T io.Reader](r T) error { ... }
// Just use the interface
func Process(r io.Reader) error { ... }
Don't Over-Constrain
// 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 when interfaces and generics both seem to fit, and you have to choose.
Type Aliases vs Definitions
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-Patterns
| 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 |
Verification Checklist
- At least two real, current call sites benefit from the type parameter
- An interface would not be a simpler model
- Constraint is the loosest one that compiles (
any,comparable,cmp.Orderedpreferred) - Type parameter names are conventional letters unless clarity demands more
- No
Texists only to satisfy an interface — accept the interface instead - No generic wrapper added without methods that justify it
- Doc comment explains what the type parameter must support
References
- references/constraints.md — constraint catalogue,
~and|,cmp.Ordered, type inference - references/generics-vs-interfaces.md — picking between a generic and an interface