go-generics — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited go-generics (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
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.
comparable, cmp.Ordered, any) over hand-rolled unions.comparable is usually enough; the narrower the constraint, the fewer callers benefit.T, K, V, E) unless a longer name genuinely helps.func F[T io.Reader](r T) is just func F(r io.Reader).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
slices.Index, maps.Keys, slices.SortFunc.any would be both ugly and slow.| 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
}~int means "anything whose underlying type is int" — covers type Celsius int.| unions widen the set.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.// 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 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 setType 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 |
any, comparable, cmp.Ordered preferred)T exists only to satisfy an interface — accept the interface instead~ and |, cmp.Ordered, type inference~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.