Lesson 11 of 12

Generics

Learn how to write and types that work with many types using type and constraints, available since Go 1.18.

Go
package main

import (
	"fmt"
	"cmp"
)

// [T any] declares a type parameter. any is an alias for interface{}.
func Map[T, U any](items []T, fn func(T) U) []U {
	out := make([]U, 0, len(items))
	for _, item := range items {
		out = append(out, fn(item))
	}
	return out
}

func Filter[T any](items []T, keep func(T) bool) []T {
	var out []T
	for _, item := range items {
		if keep(item) {
			out = append(out, item)
		}
	}
	return out
}

func Reduce[T, A any](items []T, initial A, fn func(A, T) A) A {
	acc := initial
	for _, item := range items {
		acc = fn(acc, item)
	}
	return acc
}

func main() {
	nums := []int{1, 2, 3, 4, 5}

	doubled := Map(nums, func(n int) int { return n * 2 })
	labels := Map(nums, func(n int) string { return fmt.Sprintf("#%d", n) })
	evens := Filter(nums, func(n int) bool { return n%2 == 0 })
	sum := Reduce(nums, 0, func(a, n int) int { return a + n })

	fmt.Println(doubled, labels, evens, sum)
}
Go
package main

import (
	"cmp"
	"fmt"
	"golang.org/x/exp/constraints"    // or declare your own constraint
)

// A constraint is an interface listing the permitted types.
type Number interface {
	~int | ~int64 | ~float32 | ~float64      // ~ also allows named types with that base
}

func Sum[T Number](values []T) T {
	var total T                              // zero value of whatever T is
	for _, v := range values {
		total += v
	}
	return total
}

// cmp.Ordered covers every type supporting < and >
func Max[T cmp.Ordered](values []T) (T, bool) {
	var zero T
	if len(values) == 0 {
		return zero, false
	}
	best := values[0]
	for _, v := range values[1:] {
		if v > best {
			best = v
		}
	}
	return best, true
}

func main() {
	fmt.Println(Sum([]int{1, 2, 3}))            // 6
	fmt.Println(Sum([]float64{1.5, 2.5}))       // 4
	fmt.Println(Max([]string{"b", "a", "c"}))   // c true
}
Go
package main

import (
	"fmt"
	"sync"
)

// Generic types: a type-safe, concurrency-safe cache
type Cache[K comparable, V any] struct {
	mu    sync.RWMutex
	items map[K]V
}

func NewCache[K comparable, V any]() *Cache[K, V] {
	return &Cache[K, V]{items: make(map[K]V)}
}

func (c *Cache[K, V]) Set(key K, value V) {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.items[key] = value
}

func (c *Cache[K, V]) Get(key K) (V, bool) {
	c.mu.RLock()
	defer c.mu.RUnlock()
	v, ok := c.items[key]
	return v, ok
}

// A generic stack
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() {
	c := NewCache[string, int]()
	c.Set("hits", 42)
	fmt.Println(c.Get("hits"))       // 42 true

	var s Stack[string]
	s.Push("a"); s.Push("b")
	fmt.Println(s.Pop())             // b true
}
Finished reading?Mark it done to track your progress.

Tip: use the ← and → keys to move between lessons.