Go 泛型

Go 泛型:类型参数、类型约束与编译期实例化

Go 1.18 引入泛型:函数和类型用方括号声明类型参数,用接口与类型集限定参数允许的类型。同一份源码按不同具体类型编译出多份实现,类型检查发生在编译期,运行期零开销。

基本语法:类型参数与类型约束

泛型函数在函数名后声明类型参数 [T any],any 表示不限类型;类型参数可以同时用于函数参数、返回值与局部变量:

// 泛型函数:交换两个变量
func Swap[T any](a, b *T) {
    *a, *b = *b, *a
}

// 泛型类型:栈
type Stack[T any] []T

func (s *Stack[T]) Push(v T) { *s = append(*s, v) }

comparable 是另一个内置约束,要求类型支持 == 和 !=。自定义约束用类型集:接口体里直接列出允许的类型,用 | 分隔:

type Number interface {
    int | int64 | float64   // 类型集:只接受这些类型
}

func Add[T Number](a, b T) T { return a + b }

Add(3, 5)        // OK,T 推断为 int
Add(3.14, 2.71)  // OK,T 推断为 float64

类型集可以更进一步:~int 表示”int 以及所有以 int 为底层类型的类型”,比裸写 int 覆盖面更广。

泛型基本结构:类型参数与类型集约束
图 1:泛型基本结构——类型参数 + 类型集约束,编译期按类型实例化

主要应用场景

1. 集合操作

Filter、Map 这类通用算法是泛型最直接的收益:以前每种类型写一遍,现在一份实现通吃,且调用点类型推断让使用成本为零:

func Filter[T any](s []T, f func(T) bool) []T {
    var result []T
    for _, v := range s {
        if f(v) { result = append(result, v) }
    }
    return result
}

func Map[T, U any](s []T, f func(T) U) []U {
    result := make([]U, len(s))
    for i, v := range s { result[i] = f(v) }
    return result
}

even := Filter([]int{1, 2, 3, 4, 5}, func(n int) bool { return n%2 == 0 })
squares := Map([]int{1, 2, 3}, func(n int) int { return n * n })

2. 通用数据结构

类型参数同样适用于 struct 和方法接收者。典型例子是通用缓存:K comparable 保证能做 map 键,V any 放任意值:

type Cache[K comparable, V any] struct {
    data map[K]V
}

func (c *Cache[K, V]) Get(key K) (V, bool) {
    v, ok := c.data[key]
    return v, ok
}

3. 泛型排序

约束体系里没有 < 运算符接口,想按大小排序得自己声明约束:

func SortSlice[T interface{ ~int | ~float64 | ~string }](s []T) {
    sort.Slice(s, func(i, j int) bool { return s[i] < s[j] })
}

Go 1.21 起标准库提供 cmp.Ordered,不用再手写这类类型集:

import "cmp"
func SortAny[T cmp.Ordered](s []T) { sort.Slice(s, func(i, j int) bool { return s[i] < s[j] }) }

泛型 vs interface{}(反射)

泛型之前,通用代码靠 interface{} + 类型断言:类型检查推迟到运行期,断言失败直接 panic,还有装箱开销。泛型把检查移到编译期,按具体类型实例化,运行期与手写版本等价:

// 传统方式:interface{} + 类型断言,运行期才检查,性能差
func PrintAll(vals []interface{}) { ... }

// 泛型:编译期类型检查,无反射开销
func PrintAll[T any](vals []T) { ... }
泛型与 interface{} 的编译期与运行期对比
图 2:泛型在编译期完成类型检查与实例化,interface{} 留到运行期

注意事项

  • 优先用于:多类型容器、通用算法(过滤/映射/排序)、通用数据结构;单一类型场景用泛型只增加阅读负担;
  • 约束尽量宽松:能用 comparable 就别自定义更严的类型集,调用方少受限;
  • 编译时间:泛型会略微增加编译时间(约 15–18%),Go 1.23+ 有改善;
  • 报错定位:类型推断失败时显式写出类型参数(Add[int](a, b)),编译器报错更明确;
  • 工具链:需要 Go 1.18+,IDE 支持(GoLand 2022.1+ / 新版 VSCode)。

总结

泛型是 Go 1.18 以来最大的语言能力升级:编译期类型安全 + 代码复用 + 接近手写特化的性能。集合操作、通用数据结构、算法模板是主战场;配合 Go 1.21 的 cmp.Ordered 和标准库 slices/maps,日常写泛型代码已经足够顺手。

滚动至顶部