| name | go-generics |
| description | Use when deciding whether to use Go generics, writing generic functions or types, choosing constraints, or picking between type aliases and type definitions. Also use when a user is writing a utility function that could work with multiple types, even if they don't mention generics explicitly. Does not cover interface design without generics (see go-interfaces). |
| license | Apache-2.0 |
| compatibility | Requires Go 1.18+ (generics were introduced in Go 1.18) |
| metadata | {"sources":"Google Style Guide"} |
Go 泛型与类型参数
何时使用泛型
从具体类型开始。只在出现第二种类型时才进行泛化。
优先使用泛型的场景
- 多种类型共享相同的逻辑(排序、过滤、map/reduce)
- 否则需要依赖
any 和大量的类型切换
- 正在构建可复用的数据结构(并发安全的集合、有序映射)
避免使用泛型的场景
- 实践中只有一种类型被实例化
- 接口已经能清晰地表达共享行为
- 泛型代码比特定类型的替代方案更难阅读
"写代码,不要设计类型。"—— Robert Griesemer 和 Ian Lance Taylor
决策流程
多种类型是否共享相同的逻辑?
├─ 否 → 使用具体类型
├─ 是 → 它们是否共享一个有用的接口?
│ ├─ 是 → 使用接口
│ └─ 否 → 使用泛型
不好:
func Sum[T constraints.Integer | constraints.Float](vals []T) T {
var total T
for _, v := range vals {
total += v
}
return total
}
好:
func SumInts(vals []int) int {
var total int
for _, v := range vals {
total += v
}
return total
}
类型参数命名
| 名称 | 典型用途 |
|---|
T | 通用类型参数 |
K | 映射键类型 |
V | 映射值类型 |
E | 元素/项目类型 |
对于复杂约束,可以使用简短的描述性名称:
func Marshal[Opts encoding.MarshalOptions](v any, opts Opts) ([]byte, error)
类型别名 vs 类型定义
类型别名(type Old = new.Name)很少使用——仅用于包迁移或渐进式 API 重构。
约束组合
使用 ~(底层类型)和 |(联合)组合约束:
type Numeric interface {
~int | ~int8 | ~int16 | ~int32 | ~int64 |
~float32 | ~float64
}
func Sum[T Numeric](vals []T) T {
var total T
for _, v := range vals {
total += v
}
return total
}
使用 constraints 包或 cmp 包(Go 1.21+)中的标准约束如 cmp.Ordered,而不是自己编写。
在编写自定义类型约束、使用 ~ 和 | 组合约束或调试类型推断问题时,阅读 references/CONSTRAINTS.md。
常见陷阱
不要包装标准库类型
type Set[T comparable] struct {
m map[T]struct{}
}
seen := map[string]struct{}{}
泛型在消除多个调用点之间的重复时才能证明其复杂度的合理性。单次使用的泛型只是多余的间接层。
不要为接口满足而使用泛型
func Process[T io.Reader](r T) error { ... }
func Process(r io.Reader) error { ... }
避免过度约束
func Contains[T interface{ ~int | ~string }](slice []T, target T) bool { ... }
func Contains[T comparable](slice []T, target T) bool { ... }
快速参考
| 主题 | 指导 |
|---|
| 何时使用泛型 | 仅在多种类型共享相同逻辑且接口不够用时 |
| 起点 | 先写具体代码;之后再泛化 |
| 命名 | 单个大写字母(T、K、V、E) |
| 类型别名 | 相同类型,替代名称;仅用于迁移 |
| 约束组合 | 使用 ~ 表示底层类型,` |
| 常见陷阱 | 不要对单次使用的代码或接口已足够时使用泛型 |
相关技能