可是因为 Go 对泛型的支持时间跨度太大,有非常多的以 “泛型” 为关键字的文章都是在介绍 Go1.18 之前的旧泛型提案或者设计,而很多设计最终在 Go1.18 中被废弃或发生…

2022 年 3 月 15 日,争议非常大但同时也备受期待的泛型终于伴随着 Go1.18 发布了。

可是因为 Go 对泛型的支持时间跨度太大,有非常多的以 “泛型” 为关键字的文章都是在介绍 Go1.18 之前的旧泛型提案或者设计,而很多设计最终在 Go1.18 中被废弃或发生了更改。并且很多介绍 Go1.18 泛型的文章 (包括官方的) 都过于简单,并没对 Go 的泛型做完整的介绍,也没让大家意识到这次 Go 引入泛型给语言增加了多少复杂度(当然也可能单纯是我没搜到更好的文章)

出于这些原因,我决定参考 The Go Programming Language Specification ,写一篇比较完整系统介绍 Go1.18 泛型的文章。这篇文章可能是目前介绍 Go 泛型比较全面的文章之一了

💡 本文力求能让未接触过泛型编程的人也能较好理解 Go 的泛型,所以行文可能略显啰嗦。但是请相信我,看完这篇文章你能获得对 Go 泛型非常全面的了解

1. 一切从函数的形参和实参说起

假设我们有个计算两数之和的函数

func Add(a int, b int) int {
    return a + b
}

这个函数很简单,但是它有个问题——无法计算 int 类型之外的和。如果我们想计算浮点或者字符串的和该怎么办?解决办法之一就是像下面这样为不同类型定义不同的函数

func AddFloat32(a float32, b float32) float32 {
    return a + b
}

func AddString(a string, b string) string {
    return a + b
}

可是除此之外还有没有更好的方法?答案是有的,我们可以来回顾下函数的 形参 (parameter)实参 (argument) 这一基本概念:

func Add(a int, b int) int {

    return a + b
}

Add(100,200)

我们知道,函数的 形参 (parameter) 只是类似占位符的东西并没有具体的值,只有我们调用函数传入实参 (argument) 之后才有具体的值。

那么,如果我们将 形参 实参 这个概念推广一下,给变量的类型也引入和类似形参实参的概念的话,问题就迎刃而解:在这里我们将其称之为 类型形参 (type parameter)类型实参 (type argument),如下:

func Add(a T, b T) T {
    return a + b
}

在上面这段伪代码中, T 被称为 类型形参 (type parameter), 它不是具体的类型,在定义函数时类型并不确定。因为 T 的类型并不确定,所以我们需要像函数的形参那样,在调用函数的时候再传入具体的类型。这样我们不就能一个函数同时支持多个不同的类型了吗?在这里被传入的具体类型被称为 类型实参 (type argument):

下面一段伪代码展示了调用函数时传入类型实参的方式:

Add[T=int](100, 200)

func Add( a int, b int) int {
    return a + b
}

Add[T=string]("Hello", "World")

func Add( a string, b string) string {
    return a + b
}

通过引入 类型形参类型实参 这两个概念,我们让一个函数获得了处理多种不同类型数据的能力,这种编程方式被称为 泛型编程