agentsclimarketplace

Go generics

Skill muratmirgun/gophers/skills/go-generics

26 production-grade Go skills for Claude Code, Gemini CLI, and opencode.

Install
npx -y skills add muratmirgun/gophers --skill go-generics

Assembled 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

  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.

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 any would be both ugly and slow.
  • Map/Reduce-style helpers that genuinely apply to many element types.

Type Parameter Naming

NameTypical use
TGeneral element / first type
KMap key
VMap value
EElement of a collection
RResult 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
}
  • ~int means "anything whose underlying type is int" — covers type 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-patternWhy it hurtsDo this instead
Generic for a single instantiationIndirection without payoffConcrete code
Generic where an interface fitsType parameter is just io.Reader in disguiseAccept the interface
interface{ ~int } when comparable sufficesRestricts callers, no benefitLoosen the constraint
Custom Numeric constraintcmp.Ordered existsStandard constraint
Set[T] wrapper around map[T]struct{}Two-line struct, no methodsUse the map directly
Generic function with two type params, neither usedThe compiler can infer nothingDrop 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.Ordered preferred)
  • Type parameter names are conventional letters unless clarity demands more
  • No T exists 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

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.