mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
729 字
2 分钟
为什么 Go 语言没有泛型
2023-05-04

Go 语言自 2009 年诞生以来,长期没有泛型支持。直到 Go 1.18 才正式引入泛型。为什么 Go 一开始不支持泛型?这是设计缺陷还是有意为之?

一、泛型是什么?#

1.1 泛型的价值#

泛型允许编写类型抽象的代码:

// 没有泛型:每种类型都要写一遍
func SumInts(a []int) int { ... }
func SumFloats(a []float64) float64 { ... }
// 有泛型:一套代码处理所有类型
func Sum[T any](a []T) T { ... }

1.2 泛型的代价#

// 泛型带来的复杂性
type Container[T any] struct {
items []T
}
func (c *Container[T]) Get() T { ... }
// 接口泛型
type Adder[T any] interface {
Add(T, T) T
}

泛型增加了类型系统的复杂性

二、Go 设计哲学#

2.1 Go 的核心设计原则#

flowchart LR S[简单性] --> P[显式优于隐式] P --> P2[组合优于继承] S --> F[显式错误处理] style S fill:#f96 style P fill:#ff9 style F fill:#ff9

Go 的设计目标

  • 简单、显式的代码
  • 快速编译
  • 良好的并发支持

2.2 Rob Pike 的观点#

“Go is about orthogonal, simple things that compose.” — Rob Pike

泛型的复杂性可能打破这种简洁

三、Go 没有泛型时的替代方案#

3.1 接口 + 类型断言#

// 使用空接口
type Any interface{}
func SumAny(a []Any) Any {
var sum Any
for _, v := range a {
// 类型断言
switch v := v.(type) {
case int:
sum = sum.(int) + v
case float64:
sum = sum.(float64) + v
}
}
return sum
}

问题:类型不安全,运行时错误。

3.2 代码生成#

// 生成器生成具体类型版本
//go:generate stringer -type=Command
// 生成后
type Command int
const (
CmdStart Command = iota
CmdStop
CmdRestart
)

问题:需要额外的构建步骤。

3.3 reflect 包#

import "reflect"
func DeepCopy(src interface{}) interface{} {
return reflect.DeepClone(src)
}

问题:性能差,无编译时检查。

四、为什么一开始不加泛型?#

4.1 优先级问题#

Go 的首要任务是解决并发问题

// Go 的核心创新:goroutine + channel
func worker() {
ch := make(chan int)
go func() {
ch <- 42
}()
result := <-ch
}

泛型不是必需的

4.2 复杂性与简单性的权衡#

添加泛型的成本Go 的选择
类型系统复杂性保持简单
编译时间增加保持快速编译
学习曲线保持易学
实现难度晚些添加

4.3 Go 团队的观点#

“The decision to leave generics out was deliberate. We considered it carefully, and we believe it makes Go better.” — Rob Pike

五、泛型的缺失带来的问题#

5.1 集合操作#

// 每次都要自己写
func MapInts(in []int, f func(int) int) []int {
out := make([]int, len(in))
for i, v := range in {
out[i] = f(v)
}
return out
}
// 无法写成通用的 Map

5.2 容器类型#

// Go 1.0 时代的 "官方" 做法
type IntList struct {
data []int
}
type StringList struct {
data []string
}
// 大量重复代码

六、Go 1.18 引入泛型#

6.1 泛型语法#

// 类型参数
func Map[T any](slice []T, fn func(T) T) []T {
result := make([]T, len(slice))
for i, v := range slice {
result[i] = fn(v)
}
return result
}
// 使用
numbers := []int{1, 2, 3}
doubled := Map(numbers, func(n int) int { return n * 2 })

6.2 泛型约束#

// 使用约束限制类型
type Addable interface {
int | int8 | int16 | int32 | int64 |
float32 | float64
}
func Sum[T Addable](a []T) T {
var sum T
for _, v := range a {
sum += v
}
return sum
}

6.3 泛型容器#

// 标准库新增的泛型容器
type Set[T comparable] struct {
items map[T]struct{}
}
// 使用
strings := Set[string]{}
strings.Add("hello")

七、泛型引入的时机考量#

7.1 为什么是 Go 1.18?#

2019: Go 2 提案开始讨论泛型
2020: Go 1.17 实验性泛型
2021: Go 1.18 正式发布泛型

等待的原因

  • Go 已经足够成功,不需要急着添加
  • 社区对泛型的需求已经明确
  • 有足够时间设计好的语法

7.2 经验教训#

语言泛型设计
Java 1.5后来才添加,兼容性良好
C++STL 模板,复杂度高
Rust所有权系统 + trait,独特但复杂
Go等待并学习,简洁的泛型

八、总结#

Go 一开始没有泛型的原因:

原因说明
设计优先级并发是核心创新,泛型不是
保持简单类型系统不应过于复杂
编译速度泛型会增加编译时间
设计时间需要充分思考好的设计方案
社区驱动等社区需求明确后再添加

Go 的做法:不急于添加功能,而是等到有清晰的设计和明确的社区需求时再添加。

参考引用#

支持与分享

如果这篇文章对你有帮助,欢迎支持作者或分享给更多人

为什么 Go 语言没有泛型
https://blog.souloss.com/posts/why-the-design/why-go-has-no-generics/
作者
Souloss
发布于
2023-05-04
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时