cxuu/go-generics
Use when deciding whether to use Go generics, writing generic functions or types, choosing constraints, or picking between type aliases and type definitions. Also use when a user is writing a utility function that could work with multiple types, even if they don't mention generics explicitly. Does not cover interface design without generics (see go-interfaces).
npx skills add https://github.com/cxuu/golang-skills --skill go-generics
> Compatibility: Generics require Go 1.18+.
references/CONSTRAINTS.md - Read when composing constraints, using type sets, or choosing between generics and interfaces.Start with concrete types. Generalize only when a second type appears.
any and excessive type switching> "Write code, don't design types." — Robert Griesemer and Ian Lance Taylor
Do multiple types share identical logic?
├─ No → Use concrete types
├─ Yes → Do they share a useful interface?
│ ├─ Yes → Use an interface
│ └─ No → Use generics
Bad:
// Premature generics: only ever called with int
func Sum[T constraints.Integer | constraints.Float](vals []T) T {
var total T
for _, v := range vals {
total += v
}
return total
}
Good:
func SumInts(vals []int) int {
var total int
for _, v := range vals {
total += v
}
return total
}
| Name | Typical Use |
|------|-------------|
| T | General type parameter |
| K | Map key type |
| V | Map value type |
| E | Element/item type |
For complex constraints, a short descriptive name is acceptable:
func Marshal[Opts encoding.MarshalOptions](v any, opts Opts) ([]byte, error)
Type aliases (type Old = new.Name) are rare — use only for package migration
or gradual API refactoring.
Combine constraints with ~ (underlying type) and | (union):
type Numeric interface {
~int | ~int8 | ~int16 | ~int32 | ~int64 |
~float32 | ~float64
}
func Sum[T Numeric](vals []T) T {
var total T
for _, v := range vals {
total += v
}
return total
}
Use the constraints package or cmp package (Go 1.21+) for standard constraints
like cmp.Ordered instead of writing your own.
// Bad: generic wrapper adds complexity without value
type Set[T comparable] struct {
m map[T]struct{}
}
// Better: use map[T]struct{} directly when the usage is simple
seen := map[string]struct{}{}
Generics justify their complexity when they eliminate duplication across
multiple call sites. A single-use generic is just indirection.
// Bad: T is only used to satisfy an interface — just use the interface
func Process[T io.Reader](r T) error { ... }
// Good: accept the interface directly
func Process(r io.Reader) error { ... }
// Bad: constraint is more restrictive than needed
func Contains[T interface{ ~int | ~string }](slice []T, target T) bool { ... }
// Good: comparable is sufficient
func Contains[T comparable](slice []T, target T) bool { ... }
| Topic | Guidance |
|-------|----------|
| When to use generics | Only when multiple types share identical logic and interfaces don't suffice |
| Starting point | Write concrete code first; generalize later |
| Naming | Single uppercase letter (T, K, V, E) |
| Type aliases | Same type, alternate name; use only for migration |
| Constraint composition | Use ~ for underlying types, | for unions; prefer cmp.Ordered over custom |
| Common pitfall | Don't genericize single-use code or when interfaces suffice |
Take cxuu/go-generics from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.