Skip to main content
eLearner.app
Module 10 · Lesson 3 of 548/50 in the course~15 min
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

Go
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 of interface{} since Go 1.18).
  • Inference: calling Map([]int{...}, ...) you don't have to write Map[int, int](...): the compiler infers T=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

ConstraintMeaning
anyAny type (== interface{}).
comparableTypes with == and !=: numeric, string, bool, pointers, channels, comparable arrays/structs.

comparable allows equality but not < or >.

Go
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:

Go
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 as type X int" (with the same underlying type). Without ~, only exact int.

A constraint can also mix methods and type elements:

Go
type Stringable interface {
    ~string
    Len() int
}

Generic types

Not only functions: structs and interfaces can also have type parameters.

Go
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

Exercise#go.m10.l3.e1
Attempts: 0Loading…

Definisci la funzione generica Min[T Ordered](a, b T) T che ritorna il minore usando l'operatore <. Il constraint Ordered è già fornito.

Loading editor…

Solution available after 3 attempts

Exercise#go.m10.l3.e2
Attempts: 0Loading…

Implementa Map[T, U any](s []T, f func(T) U) []U: applica f a ogni elemento e restituisce il nuovo slice.

Loading editor…

Solution available after 3 attempts

Quiz#go.m10.l3.e3
Ready

Quale constraint built-in consente l'operatore == ma NON l'operatore <?

Go
func Index[T ???](s []T, x T) int { ... }
Answer options