Go 语言泛型

类型参数、约束、`~` 底层类型、以及什么时候不该用泛型。

Advanced
Key PointsGo 1.18 引入泛型:`[T any]` 声明类型参数,用接口描述约束。

第一个泛型函数

Go
// 没有泛型时要为每种类型写一遍
func SumInts(m []int) int {
	var s int
	for _, v := range m {
		s += v
	}
	return s
}

// 有了泛型,一个就够
func Sum[T int | int64 | float64](m []T) T {
	var s T
	for _, v := range m {
		s += v
	}
	return s
}

fmt.Println(Sum([]int{1, 2, 3}))
fmt.Println(Sum([]float64{1.5, 2.5}))
Output
6
4

约束

Go
import "golang.org/x/exp/constraints"

// 用标准库约束
func Max[T constraints.Ordered](a, b T) T {
	if a > b {
		return a
	}
	return b
}

// 自定义约束接口
type Number interface {
	~int | ~int64 | ~float64
}

func Avg[T Number](m []T) float64 {
	var s T
	for _, v := range m {
		s += v
	}
	return float64(s) / float64(len(m))
}

fmt.Println(Max(3, 7))
fmt.Println(Avg([]int{1, 2, 3, 4}))
Output
7
2.5

The ~ in ~int means "any type whose underlying type is int", including type MyInt int. Without ~ only int itself matches.

泛型类型

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

s := &Stack[string]{}
s.Push("a")
s.Push("b")
v, _ := s.Pop()
fmt.Println(v)
Output
b

comparable

Go
// 需要 == 或做 map 键时,约束必须是 comparable
func Contains[T comparable](s []T, target T) bool {
	for _, v := range s {
		if v == target {
			return true
		}
	}
	return false
}

fmt.Println(Contains([]string{"a", "b"}, "b"))
fmt.Println(Contains([]int{1, 2}, 3))

// 用泛型写 map 的键
func Keys[K comparable, V any](m map[K]V) []K {
	out := make([]K, 0, len(m))
	for k := range m {
		out = append(out, k)
	}
	return out
}
Output
true
false
comparable excludes slices, maps and functions — they are not comparable. Constraining to any then using == fails to compile, the most common generics error.

什么时候不该用泛型

SituationUse
只有一种类型普通函数
行为不同、只是签名像接口
运行时才知道类型any + 断言
多个类型有相同的算法泛型
容器类型(栈、队列、缓存)泛型

The Go team is conservative here: generics exist for containers and generic algorithms, not to erase all duplication. If a function is used once, or behaviour differs per type, generics only hurt readability.

标准库里的泛型

Go
import (
	"cmp"
	"maps"
	"slices"
)

s := []int{3, 1, 4, 1, 5}
slices.Sort(s)
fmt.Println(s)

fmt.Println(slices.Max(s), slices.Min(s))
fmt.Println(slices.Contains(s, 4))

m := map[string]int{"b": 2, "a": 1}
fmt.Println(slices.Sorted(maps.Keys(m)))

fmt.Println(cmp.Compare(1, 2))
Output
[1 1 3 4 5]
5 1
true
[a b]
-1

Since Go 1.21 the slices, maps and cmp packages cover the generic operations you used to hand-write. Check them before writing your own.

Friends & Partners