Go 泛型(Generics)底层实现
一、Go 泛型到底是什么?
一句话:
泛型 = 模板 + 编译期生成具体代码 + 不侵入运行时
Go 泛型不是像 Java 那样擦除,
而是编译时 “展开模板” ,生成具体类型的函数 / 结构体。
大白话:
// 泛型模板
func Add[T int | float64](a, b T) T
// 编译后 → 自动生成两份真实代码
func Add_int(a, b int) int
func Add_float64(a, b float64) float64
Go 泛型 = 编译期多版本生成 + 运行时无损耗
二、Go 泛型底层三大核心
- 类型参数(Type Parameter) :[T int | string]
- 类型集合(Type Set) :T 可以是什么类型
- 实例化(Instantiation) :编译期把 T 替换成真实类型(int/string 等)
三、Go 泛型底层源码结构
Go 泛型完全在编译期实现,运行时(runtime)几乎无代码。
核心都在 cmd/compile 里。
最核心、最关键的底层结构。
1. 泛型函数 / 类型的底层表示
// 泛型“模板”的底层结构
type TypeParam struct {
name *types.Name // 参数名 T
index int // 是第几个泛型参数
bound types.Type // 约束:T 必须满足什么类型(int|float等)
}
// 泛型函数的底层表示
type Func struct {
tparams []*TypeParam // 泛型参数列表 [T E]
body *Node // 函数体(模板)
inst map[string]*Func // 缓存:已经实例化的版本(如 T=int, T=string)
}
2. 实例化(最核心)
// 实例化:把 T 替换成 int,生成真实函数
func Instantiate(fun *Func, args []types.Type) *Func {
// 1. 检查类型是否满足约束
checkTypeConstraints(args, fun.tparams)
// 2. 生成唯一key(如 Add_int_int)
key := makeInstKey(fun, args)
// 3. 从缓存取,避免重复生成
if f := fun.inst[key]; f != nil {
return f
}
// 4. 替换模板所有 T → 真实类型(int/float64)
newFunc := replaceTypeParams(fun, args)
// 5. 编译、生成汇编、加入缓存
compile(newFunc)
fun.inst[key] = newFunc
return newFunc
}
3. 类型约束检查
// 检查 T 是否满足约束(int|float64)
func checkTypeConstraints(actual []types.Type, tparams []*TypeParam) {
for i := range actual {
t := actual[i]
bound := tparams[i].bound
// 真实类型 是否 实现/匹配 约束
if !typeImplementsBound(t, bound) {
errorf("类型 %v 不满足约束 %v", t, bound)
}
}
}
四、总结
- 泛型 = 模板
- 调用时 = 编译期自动生成具体版本
- 运行时 = 普通函数,无任何损耗
- 不擦除类型,不虚化,不反射
- 一个类型,生成一份代码
五、Go 泛型完整流程图
六、泛型执行
| 方式 | 原理 | 性能 | 适用场景 |
|---|---|---|---|
| 泛型 | 编译期生成代码 | 最快 | 通用数据结构、工具函数 |
| 接口 | 运行时动态派发 | 中等 | 多态、依赖倒置 |
| 反射 | 运行时解析类型 | 最慢 | 序列化、框架 |
1. 写泛型代码
func Add[T int | float64](a, b T) T {
return a + b
}
编译器存成模板。
2. 调用泛型函数
Add(1, 2)
编译器:
- 看到 T=int
- 检查 int 是否满足约束
- 生成 Add_int 函数
3. 运行
运行时执行的就是普通函数,
没有泛型、没有反射、没有额外开销。
七、Go 泛型最重要 3 个特点
- 编译期展开,运行时无损耗
- 不做类型擦除(和 Java 完全不同)
- 一个类型一份代码,速度极快
八、你最关心的问题
Q:泛型性能如何?
A:和手写函数完全一样,无任何损耗。
Q:泛型会生成很多代码吗?
A:只会生成你实际用到的类型版本。
Q:泛型和接口区别?
A:接口是运行时动态派发,
泛型是编译期静态生成,
泛型更快。
九、总结
- 泛型 = 编译期模板展开
- 实例化 = 生成具体类型函数
- 运行时 = 普通函数,零损耗
- 不擦除、不反射、不慢
- Go 泛型 = 静态编译 + 高性能