Module lessons (3/5)
Generics (Go 1.18+)
Since Go 1.18 (March 2022), type parameters are available: functions and types can be parameterized over types. They let you write Map, Filter, Set[T], LinkedList[T] without duplicating code and without interface{} + runtime type assertions.
Generic function: the syntax
func Map[T, U any](s []T, f func(T) U) []U {
out := make([]U, len(s))
for i, v := range s {
out[i] = f(v)
}
return out
}
doubled := Map([]int{1, 2, 3}, func(n int) int { return n * 2 })
// doubled is []int{2, 4, 6}Three new elements:
[T, U any]after the name: declares the type parameters with their constraints.any= constraint that accepts any type (alias ofinterface{}since Go 1.18).- Inference: calling
Map([]int{...}, ...)you don't have to writeMap[int, int](...): the compiler infersT=int, U=int.
Sometimes inference is not enough (e.g., constructors with no T arguments) and explicit instantiation is needed: New[*Server]().
Built-in constraints
| Constraint | Meaning |
|---|---|
any | Any type (== interface{}). |
comparable | Types with == and !=: numeric, string, bool, pointers, channels, comparable arrays/structs. |
comparable allows equality but not < or >.
func Index[T comparable](s []T, x T) int {
for i, v := range s {
if v == x { return i }
}
return -1
}For </> you need a custom constraint.
Custom constraint: type unions
A constraint is an interface with "type element" clauses:
type Ordered interface {
~int | ~int64 | ~float64 | ~string
}
func Min[T Ordered](a, b T) T {
if a < b { return a }
return b
}|= union: accepts any of the listed types.~int= "int or any type defined astype X int" (with the same underlying type). Without~, only exactint.
A constraint can also mix methods and type elements:
type Stringable interface {
~string
Len() int
}Generic types
Not only functions: structs and interfaces can also have type parameters.
type Set[T comparable] struct {
m map[T]struct{}
}
func NewSet[T comparable]() *Set[T] {
return &Set[T]{m: make(map[T]struct{})}
}
func (s *Set[T]) Add(v T) { s.m[v] = struct{}{} }
func (s *Set[T]) Has(v T) bool { _, ok := s.m[v]; return ok }
s := NewSet[string]()
s.Add("ada")Methods CANNOT add type parameters beyond those of the type: func (s *Set[T]) MapTo[U any](...) does not compile (intentional design limitation).
When to use generics
Yes when:
- The logic is truly identical for several types (collections, algorithms, helpers like
Map/Filter/Reduce). - The alternative would be duplicating code or using
interface{}+ type assertions (losing type safety).
No when:
- An interface with one or two methods is enough and more readable (
io.Reader,fmt.Stringer). - You're parameterizing "because you can": a single use case → make a concrete version of it.
Exercises
Definisci la funzione generica Min[T Ordered](a, b T) T che ritorna il minore usando l'operatore <. Il constraint Ordered è già fornito.
Solution available after 3 attempts
Implementa Map[T, U any](s []T, f func(T) U) []U: applica f a ogni elemento e restituisce il nuovo slice.
Solution available after 3 attempts
Quale constraint built-in consente l'operatore == ma NON l'operatore <?
func Index[T ???](s []T, x T) int { ... }