Generic(泛型)
泛型允许函数和类型使用类型参数,从而复用算法并保留编译期类型检查。本文示例要求 Go 1.18 或更高版本。
类型参数与约束
约束规定类型参数允许的类型以及泛型代码能够使用的操作:
package main
import "fmt"
type Number interface{ ~int | ~int64 | ~float64 }
func Sum[T Number](values []T) T {
var total T
for _, value := range values {
total += value
}
return total
}
type Score int
func main() {
fmt.Println(Sum([]Score{1, 2, 3})) // ~ 允许底层类型为 int 的定义类型。
}
没有 ~ 时,约束只接受列出的具体类型;有 ~ 时,还接受底层类型相同的定义类型。any 等于 interface{},不保证类型支持比较、排序或算术运算。comparable 只表达可比较性,不能表达大小顺序。
类型推断与显式实例化
编译器通常从实参推导类型参数:Sum([]int{1, 2}) 等价于 Sum[int](...)。当类型参数只出现在结果、多个参数无法统一或需要明确 API 意图时,可以显式写 Sum[int](values)。推断失败是编译错误,不会延迟到运行时。
泛型类型
type Stack[T any] struct{ values []T }
func (s *Stack[T]) Push(value T) { s.values = append(s.values, value) }
func (s *Stack[T]) Pop() (T, bool) {
if len(s.values) == 0 {
var zero T
return zero, false
}
last := len(s.values) - 1
value := s.values[last]
s.values = s.values[:last]
return value, true
}
泛型类型实例化为 Stack[string] 等具体类型。空栈返回 T 的零值和 false;如果元素包含指针、切片或映射,仍需考虑底层状态共享和并发安全。
何时使用泛型
泛型适合稳定、重复、只因类型不同而重复的算法或容器。若逻辑只使用一次,具体类型通常更易读;若调用方需要运行时替换行为,接口可能比类型参数更合适。泛型约束是编译期能力,不等同于运行时类型信息。
常见错误
- 约束写成
any后却假设类型支持比较或排序。 - 忽略
~,导致自定义定义类型无法传入预期的泛型函数。 - 为只使用一次的简单逻辑引入泛型,增加阅读成本。
- 忽略泛型返回值的零值语义。
- 以为泛型约束会在运行时提供类型元数据。
TypeScript 泛型主要被擦除;Go 泛型由编译器实现,具体生成策略属于实现细节,不应据此推断性能。性能结论必须通过 benchmark 验证。
小结
- 类型参数写在
[]中,约束规定可用类型和操作。 ~区分具体类型集合与底层类型集合。- 泛型适合稳定重复的类型安全算法;接口更适合运行时多态。