go从零单排之泛型

15 阅读3分钟

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 泛型底层三大核心

  1. 类型参数(Type Parameter) :[T int | string]
  1. 类型集合(Type Set) :T 可以是什么类型
  1. 实例化(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)
        }
    }
}

四、总结

  1. 泛型 = 模板
  1. 调用时 = 编译期自动生成具体版本
  1. 运行时 = 普通函数,无任何损耗
  1. 不擦除类型,不虚化,不反射
  1. 一个类型,生成一份代码

五、Go 泛型完整流程图

image.png


六、泛型执行

方式原理性能适用场景
泛型编译期生成代码最快通用数据结构、工具函数
接口运行时动态派发中等多态、依赖倒置
反射运行时解析类型最慢序列化、框架

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 个特点

  1. 编译期展开,运行时无损耗
  1. 不做类型擦除(和 Java 完全不同)
  1. 一个类型一份代码,速度极快

八、你最关心的问题

Q:泛型性能如何?

A:和手写函数完全一样,无任何损耗。

Q:泛型会生成很多代码吗?

A:只会生成你实际用到的类型版本

Q:泛型和接口区别?

A:接口是运行时动态派发

泛型是编译期静态生成

泛型更快


九、总结

  • 泛型 = 编译期模板展开
  • 实例化 = 生成具体类型函数
  • 运行时 = 普通函数,零损耗
  • 不擦除、不反射、不慢
  • Go 泛型 = 静态编译 + 高性能