Day 023 — Generics Fundamentals¶
Month 1 · Week 4 · ⬅ Day 022 · Day 024 ➡ · Journal index
🎯 Learning Objective¶
Write generic functions and types with type parameters and constraints, and know when generics help vs. when an interface is the better tool.
📚 Topics¶
- Type parameters
[T any], multiple params[K comparable, V any] - Constraints:
any,comparable, thecmp.Orderedinterface, custom union constraints (~int | ~float64) - Type inference vs explicit instantiation
- When NOT to use generics (interfaces vs. type params)
📖 Reading / Sources¶
- Go blog — An Introduction to Generics
- Tutorial — Getting started with generics
- Go spec — Type parameters & constraints
-
cmppackage docs:pkg.go.dev/cmp
📝 Notes¶
- A type parameter is a placeholder type listed in square brackets before the ordinary params:
func Index[T comparable](s []T, v T) int.Tis bound at the call site by inference or explicit instantiationIndex[string](...). - A constraint is just an interface used in a type-parameter list. It restricts which types may be substituted and which operations are legal inside the body.
any= the empty interfaceinterface{}: no operations beyond assignment/passing.comparable= types usable with==/!=(needed for map keys, set membership). Links to [[maps-sets]]. NB:comparabledoes not mean ordered.cmp.Ordered(stdlib, Go 1.21+) = types supporting< <= >= >— ints, floats, strings. Enablescmp.Less,min/maxover generics.- Union / approximation constraints:
~int | ~int64 | ~float64permits those types and any named type whose underlying type is one of them (the~means "underlying type"). This is how you write a numericSum. - Type inference: the compiler infers
Tfrom the argument types, so callers usually omit[...]. You must instantiate explicitly when inference can't see the type (e.g.Make[int]()with no args determining it). - Built-in
min/max/clear: Go 1.21 addedmin,max(variadic, work on any ordered type) andclear(maps/slices) as builtins — often you don't need a custom generic at all. - When NOT to use generics: if behavior differs per type, use an interface (dynamic dispatch). Reach for generics when the algorithm is identical and you only want to avoid
interface{}boxing and type assertions (containers, map/filter/reduce). "If you're writing the same code for[]intand[]string, generalize; if you're switching on type, use an interface." - Constraints can't be used as ordinary types: you can't write
var x cmp.Ordered. They only appear in type-parameter lists.
💻 Code Examples¶
import "cmp"
// One algorithm, any ordered element type.
func Max[T cmp.Ordered](s []T) (T, bool) {
var zero T
if len(s) == 0 {
return zero, false // empty: report not-ok rather than panic
}
m := s[0]
for _, v := range s[1:] {
if v > m { // legal because cmp.Ordered guarantees `>`
m = v
}
}
return m, true
}
// Union constraint enables `+` on numeric types (incl. named types via ~).
type Number interface{ ~int | ~int64 | ~float64 }
func Sum[T Number](s []T) T {
var total T // zero value of the element type
for _, v := range s {
total += v
}
return total
}
Full code:
examples/month-01/generics/main.go· Run:go run ./examples/month-01/generics
🏋️ Exercises / Practice¶
| Exercise | Status | Link |
|---|---|---|
Generic Map/Filter/Reduce/Sum/Keys |
✅ | exercises/month-01/week-4/genslice |
🐛 Mistakes Made¶
- Used
comparableand then trieda < binside the body → compile error.comparableonly grants==/!=; I switched the constraint tocmp.Ordered. - Forgot the
~in a union constraint, so a named typetype Celsius float64was rejected.~float64(approximation) accepts it.
❓ Open Questions¶
- Performance: does the compiler monomorphize or use dictionaries (GCShape stenciling)? When does that cost matter? (Parked — measure before worrying.)
🧠 Active Recall (answer without looking)¶
-
Q: What's the difference between
comparableandcmp.Ordered?
A
`comparable` allows `==`/`!=` only (map keys, set membership). `cmp.Ordered` additionally allows the ordering operators `< <= >= >` (ints, floats, strings). -
Q: What does the
~in a constraint like~intmean?
A
"Any type whose **underlying type** is `int`" — so it matches `int` and named types such as `type Age int`, not just `int` itself.
🪶 Feynman Reflection¶
Generics let me write an algorithm once and have the compiler stamp out a correct, type-safe version per element type — no interface{} boxing, no runtime type assertions. The constraint is the contract: it tells both the compiler and me which operations the placeholder type is allowed to do.
🕳️ Knowledge Gaps¶
- Designing my own constraint interfaces (with methods + type sets together) — I'll try it building the generic container tomorrow.
✅ Summary¶
I can declare type parameters, pick the right constraint (any / comparable / cmp.Ordered / unions with ~), rely on inference, and choose generics vs interfaces deliberately.
⏭️ Next Steps / Prep for Tomorrow¶
- Day 024: build generic data structures — a
Stack[T]and aSet[T].
| Time spent | Difficulty | Confidence |
|---|---|---|
| 95 min | 🟦🟦🟦⬜⬜ | 🟦🟦🟦⬜⬜ |
Suggested commit: feat(examples): generics fundamentals — type params and constraints (day 023)