Go 语言泛型¶
泛型允许函数和类型使用类型参数,在保持静态类型检查的同时复用算法。类型参数写在方括号中, 每个参数都必须有约束;约束描述允许的类型集合及可使用的操作。
泛型函数与类型推断¶
package main
import "fmt"
func First[T any](values []T) (T, bool) {
if len(values) == 0 {
var zero T
return zero, false
}
return values[0], true
}
func main() {
value, ok := First([]string{"Go", "Rust"})
fmt.Println(value, ok)
}
调用 First([]string{...}) 时,编译器从实参推断 T 为 string,无需写 First[string]。
如果没有足够信息,例如调用一个无参数泛型构造函数,就必须显式提供类型实参。
约束与底层类型¶
type Integer interface {
~int | ~int8 | ~int16 | ~int32 | ~int64
}
func Sum[T Integer](values []T) T {
var total T
for _, value := range values {
total += value
}
return total
}
| 表示类型集合的并集,~int 表示所有底层类型为 int 的类型,因此自定义的
type Count int 也满足约束。第一个差异示例:写 int 只允许预声明类型 int,写 ~int
还允许底层类型相同的命名类型。
any 不允许使用 + 或 <,因为不是所有类型都支持这些运算。comparable 允许 == 和 !=,
常用于 map 键或去重算法,但不意味着支持排序。
泛型类型¶
type Stack[T any] struct {
items []T
}
func (s *Stack[T]) Push(value T) {
s.items = append(s.items, value)
}
func (s *Stack[T]) Pop() (T, bool) {
if len(s.items) == 0 {
var zero T
return zero, false
}
last := len(s.items) - 1
value := s.items[last]
var zero T
s.items[last] = zero
s.items = s.items[:last]
return value, true
}
第二个差异示例:Stack[int] 与 Stack[string] 是不同的实例化类型,不能互相赋值;
非泛型的 []any 虽能混放值,却会丢失元素类型关系并需要断言。
标准库约束与辅助包¶
Go 1.21+ 的 slices、maps 和 cmp 包提供常见泛型算法。排序有序值可使用 slices.Sort,
自定义顺序使用 slices.SortFunc 和 cmp.Compare,通常无需自行维护 Ordered 约束。
类型推断边界¶
类型推断主要根据函数实参和约束统一类型,不根据返回值目标类型完成所有推断。类型参数的方法集 只包含约束保证的方法;即使实际传入类型还有其他方法,泛型函数也不能直接调用它们。
常见错误¶
- 使用
any约束后仍尝试对T做加法或排序比较。 - 忘记
~,意外拒绝底层类型相同的自定义命名类型。 - 为只有一种实际类型的代码引入泛型,增加 API 和错误信息复杂度。
- 把类型参数当作运行时模板或继承机制。
- 通过
any转换和类型 switch 绕过约束,破坏静态安全。
性能与工程实践¶
泛型通常能避免接口装箱和手写类型断言,但具体机器码生成策略是实现细节,不能假设一定零开销。 优先复用标准库泛型函数;自定义约束应保持最小,并用能表达领域含义的名称。只有算法确实对多种类型 保持相同行为时才泛化。基准测试要覆盖实际实例化类型,因为不同大小和布局的类型成本可能不同。