Intermediate · Packages, testing and generics · topic 25 of 46 · 40 min
Generics: type parameters and constraints
Explanation
The problem
Before Go 1.18, if you wanted a function that finds the biggest item in a slice, you wrote MaxInts, MaxFloats and MaxStrings, all identical except the type. Or you used any and type assertions, losing type safety. Generics solve this: write the logic once, and the compiler checks it for every type you use.
A generic function
import "cmp"
func Max[T cmp.Ordered](a, b T) T {
if a > b {
return a
}
return b
}
func main() {
fmt.Println(Max(3, 7)) // 7
fmt.Println(Max(2.5, 1.5)) // 2.5
fmt.Println(Max("chai", "coffee")) // coffee
}- Type parameter: a placeholder type name in square brackets, like [T any].
- Constraint: an interface that says what T must support. Here, ordering with < and >.
- Type inference: Go usually works out T from the arguments, so you write Max(3, 7), not Max[int](3, 7).
- Instantiation: you can also give the type explicitly: Max[float64](3, 2.5).
Common constraints
| Constraint | Means | Allows |
|---|---|---|
| any | every type | storing, passing, returning; no operators |
| comparable | types that support == and != | map keys, equality checks |
| cmp.Ordered | integers, floats and strings | < <= > >= and == |
| your own interface | a set of types and/or methods | whatever the set allows |
type Number interface {
~int | ~int64 | ~float64
}
func Sum[T Number](xs []T) T {
var total T
for _, x := range xs {
total += x
}
return total
}
type Paise int64
func main() {
fmt.Println(Sum([]int{1, 2, 3})) // 6
fmt.Println(Sum([]float64{1.5, 2.25})) // 3.75
fmt.Println(Sum([]Paise{1000, 2550})) // 3550 (works because of ~int64)
}Generic types
type Stack[T any] struct {
items []T
}
func (s *Stack[T]) Push(v T) { s.items = append(s.items, v) }
func (s *Stack[T]) Pop() (T, bool) {
var zero T
if len(s.items) == 0 {
return zero, false
}
v := s.items[len(s.items)-1]
s.items = s.items[:len(s.items)-1]
return v, true
}
func main() {
var undo Stack[string]
undo.Push("type A")
undo.Push("type B")
v, _ := undo.Pop()
fmt.Println(v) // type B
}Generics you already get for free
The standard library's slices and maps packages (Go 1.21+) are generic: slices.Sort, slices.Contains, slices.Index, maps.Keys and friends work for any suitable type. The built-in min and max, and the cmp package, use the same ideas. Often you will USE generics far more than you write them.
When not to use generics
- If the code only ever handles one type, write it for that type.
- If what differs between types is BEHAVIOUR (each type does it its own way), use an interface with methods, not a type parameter.
- Do not turn every function generic "to be flexible". It makes code harder to read and errors harder to understand.
- A good test: you are about to copy-paste a function and only change the types. That is when generics help.
Need help?
Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.
Try:
Watch the lesson
Generics: type parameters and constraints · 1/7
Generics let you write one function or type that works for many types, safely.
Prefer a video by a teacher? Find free videos on “golang generics tutorial type parameters constraints cmp.Ordered” on YouTube ↗
In real life: A typed in-memory cache for an API
A food delivery API caches restaurant menus and user profiles for a few minutes to reduce database load. One generic Cache[K, V] type works for both, with full type checking: putting a Profile into the menu cache is a compile error, and no type assertions are needed when reading.
package main
import (
"fmt"
"sync"
)
type Cache[K comparable, V any] struct {
mu sync.Mutex
data map[K]V
}
func NewCache[K comparable, V any]() *Cache[K, V] {
return &Cache[K, V]{data: make(map[K]V)}
}
func (c *Cache[K, V]) Set(k K, v V) {
c.mu.Lock()
defer c.mu.Unlock()
c.data[k] = v
}
func (c *Cache[K, V]) Get(k K) (V, bool) {
c.mu.Lock()
defer c.mu.Unlock()
v, ok := c.data[k]
return v, ok
}
type Menu struct{ Items []string }
func main() {
menus := NewCache[int, Menu]()
menus.Set(101, Menu{Items: []string{"Masala dosa", "Filter coffee"}})
if m, ok := menus.Get(101); ok {
fmt.Println(m.Items) // [Masala dosa Filter coffee]
}
}Practice (0/4 done)
What does this print? func First[T any](xs []T) T { return xs[0] } ... fmt.Println(First([]string{"Pune", "Goa"}))
Why does this not compile? func Biggest[T any](a, b T) T { if a > b { return a } return b }
type ID int64. Which constraint accepts ID?
Write generic functions Map[T, U any](xs []T, f func(T) U) []U and Filter[T any](xs []T, keep func(T) bool) []T. Use them to turn []int prices in paise into []string like "Rs 4.99", and to keep only prices above Rs 100.
Quick quiz
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- Testing with go testPackages, testing and generics · same module
- Dependencies with Go modulesPackages, testing and generics · same module
- Generics: reusable, type-safe codeC# and .NET · Collections, generics and LINQ
- GenericsJavaScript Programming (language in depth) · TypeScript basics
- GenericsJava Programming (Core to Spring Boot) · Exceptions and generics
- Generics and utility typesFrontend Web Development · TypeScript basics
Questions people ask about this
Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.
In which Go version did generics arrive?
Answer: 1.18
Go 1.18 (March 2022) added type parameters.
Which constraint lets you use a type as a map key?
Answer: comparable
Map keys need ==, which comparable guarantees.
What does ~int mean in a constraint?
Answer: int or any type whose underlying type is int
The tilde includes defined types built on int.
When is an interface better than a type parameter?
Answer: When each type behaves differently and has its own methods
Different behaviour per type is what interfaces are for.
Can a method declare its own new type parameters?
Answer: No, methods can only use the type parameters of their receiver type
Go does not allow type parameters on methods themselves.
Which is a generic type in .NET?Related
Answer: Dictionary<TKey,TValue>
Dictionary has two type parameters.
Which built-in type is generic?Related
What do generics mainly give you?Related
Answer: Type safety at compile time without casts
Errors move from run time to compile time.
From Generics
function first<T>(list: T[]): T | undefined. What is the type of first([10, 20])?Related
Answer: number | undefined
T is inferred as number from the array, so the return type is number | undefined.
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.
Next steps
- This is the last topic of “Packages, testing and generics”. The module exam comes next.
- Module 5 exam: Packages, testing and generics · 14 questions · 25 min, without help. 5 topics of this module still to finish first.
- Then the next module, Concurrency: goroutines, channels and context. 46 topics left in the course before the final exam.