Go语言版本新特性总结
Go 1.25 新特性
语言的变化
- 泛型的优化:移除了“核心类型”概念,回归类型集本质。
// 从 Go 1.25 开始,以下语法可以编译通过
type Constraint interface { ~[]byte | ~string }
func Slice[T Constraint](s T) T {
return s[1:3] // 合法:切片操作对 []byte 和 string 均有效
}工具链与开发体验
go build -asan默认开启 CGo 内存泄漏检测,提高安全性。go.mod新增ignore指令:可忽略部分目录,方便大型多语言仓库管理。go doc -http可直接启动本地文档服务器并自动打开浏览器。go version -m -json:以 JSON 格式输出构建信息,更适合自动化分析。- 仓库子目录可作为模块根路径,多模块仓库更灵活。
go vet新增hostport、waitgroup分析器,提高静态检查能力。
标准库
- 新增
testing/synctest并发测试包,支持虚拟时钟模拟、协程编排。 - 实验性特性:
encoding/json/v2,性能更优,通过环境变量显式启用。 - 加密、文件系统、网络接口等多项优化。
sync.WaitGroup.Run方法,代替原先的Add(1)+defer Done()的写法,简化并发任务管理。
运行时与 GC
GOMAXPROCS将会自动适配容器 CPU 限制,对于容器环境将会更友好,支持动态调整。- 实验性特性:
greenteagc垃圾回收器,针对小对象,大幅度降低 GC 开销。 panic优化:输出更整洁。
编译器和链接器
- DWARF 5 调试信息默认开启,内联和逃逸分析进一步优化,编译更快、体积更小,大型项目尤为显著。
- 放弃 macOS 的 10.15/11 支持,Windows arm32 将在下个版本终止支持。
Go 1.24 新特性
Go 1.24 没有太多的新特性,因此这里简单提及一些比较值得关注的变化。
类型别名支持泛型
举个例子,我们要根据map定义一个set,在 Go 1.24 中可以这样定义:
type set[P comparable] = map[P]bool标准库
bytes包新增了迭代器相关函数- 还有一些影响不大的变化,就不一一列举了
Go 1.23 新特性
语言的变化
Go 1.23 版本中,将 Go 1.22 中的关于“函数迭代器”的实验性功能正式发布。
func Range0To10(func(int) bool) {
for i := 0; i < 10; i++ {
if !cb(i) {
return
}
}
}
func main() {
for i := range Range0To10 {
fmt.Println(i)
}
}上面展示的是单参数的函数迭代器的用法,Go 1.23 版本中正式支持了零参数、单参数和双参数的函数迭代器。
func(func() bool)
func(func(K) bool)
func(func(K, V) bool)标准库
time包的调整
首先,time.Timer和time.Ticker即使没有调用Stop方法,只要不再被引用,都会被自动回收。
其次,time.Timer和time.Ticker关联的通道现在改为了无缓冲区的通道,在早期的Go版本中,这个通道有一个元素的缓冲区。
迭代器相关包
新增了iter包,提供了迭代器对应的定义。
slices包和maps包也进行了相应的调整,提供了迭代器的功能。
其它库的调整
- 补上了
math/rand/v2包中缺失的Uint函数和方法 slices包新增了Repeat函数,用以创建元素重复的切片sync.Map新增了Clear方法,用以清除其中的所有键值对sync/atomic包中新增了And和Or方法- 还有一些影响不大的变化,就不一一列举了
Go 1.22 新特性
语言的变化
Go 1.22 对for循环进行了两项更改,和一项实验性功能。
循环变量的调整
以前,for循环声明的变量只创建一次,并在每次迭代时更新。在 Go 1.22 中,循环的每次迭代都会创建新变量,以避免意外共享错误。举个例子:
ss := []string{"123", "234", "345"}
for _, s := range ss {
// 从 Go 1.22 开始,这里不需要再加一行 s := s 了
go func() {
fmt.Println(s)
}()
}对于以前的版本,上述代码大概率会输出三个"345",因为在每一遍for循环时,s是同一个变量,只不过每次循环把它更新成了新值。而使用go语句启动协程一般是需要一些时间的,协程真正开始执行会比for循环要慢一些的,此时的s已经变成了最后一个值"345",所以三个协程都会输出"345"。
而在 Go 1.22 中,上述代码会将三个字符串各输出一遍,三个字符串的输出顺序会因为协程执行的顺序而不同。
整数范围循环
现在,range后面允许是一个整数了,例如:
for i := range 10 { // 等价于 for i := 0; i < 10; i++ {
fmt.Println(i)
}(实验性功能)函数迭代器
想要使用这个功能,需要在编译时启用环境变量GOEXPERIMENT=rangefunc。
ss := []string{"123", "234", "345"}
f := func(cb func(int, string) bool) {
for i, s := range ss {
if !cb(i, s) {
return
}
}
}
for i, s := range f {
fmt.Println(i, s)
}从上述代码中可以看到,f是一个函数,它的格式为func(cb func(xxx) bool),其中xxx可以为零到两个参数,两个参数支持任意类型,我们将其称为“函数迭代器”。当我们for i, s := range f时,会将for下面的大括号体中的内容作为cb函数传入f函数中去,然后前面的i, s := 两个参数对应cb的两个形参,并且变量类型和cb的形参声明的类型也一致。
通过这种语法,我们可以方便的生成一些迭代器,例如切片的逆序迭代器:
func Backward[E any](s []E) func(func(int, E) bool) {
return func(yield func(int, E) bool) {
for i := len(s)-1; i >= 0; i-- {
if !yield(i, s[i]) {
return
}
}
}
}
func main() {
s := []string{"hello", "world"}
for i, x := range Backward(s) {
fmt.Println(i, x)
}
}go vet 工具
go vet 工具的变化
对循环变量的引用
由于上文的函数迭代器的变化,导致上述代码不会再有类似的风险,所以vet工具不再会报告这些错误。
append的新警告
现在,像s = append(s)这样不产生任何效果的append代码,会被vet工具报告错误。
在defer中错误使用time的警告
参考这样一个例子:
t := time.Now()
defer log.Println(time.Since(t)) // 事实上,time.Since并不会在defer的时候才调用。我们实际defer的是log.Println
tmp := time.Since(t); defer log.Println(tmp) // 同上
defer func() {
log.Println(time.Since(t)) // 正确的time.Since写法
}()现在,vet工具会报告出上述错误的写法。
对于log/slog的警告
slog库的正确用法是slog.Info(message, key1, v1, key2, v2),如果在key的位置填写的既不是一个string,又不是一个slog.Attr,现在vet工具会报告这个错误。
核心库
- 新增了
math/rand/v2包 - 新增了
go/version包,用以比较 Go 版本号,例如:version.Compare("go1.21rc1", "go1.21.0") net/http.ServeMux现在有了更多的支持,已经支持了传入请求方法和通配符- 一些库的小变化
archive/tar包新增了Writer.AddFS方法archive/zip包新增了Writer.AddFS方法cmp包新增了Or函数,用以返回一系列变量中的第一个非零值变量log/slog包新增了SetLogLoggerLevel函数net/http包新增了ServeFileFS,FileServerFS,NewFileTransportFS函数reflect包新增了Value.IsZero方法slices包新增了Concat函数,用以合并多个切片。Delete,Compact,Replace等函数现在会把切片末尾空出来的位置置为零值。- 还有一些影响不大的变化,就不一一列举了
Go 1.21 新特性
Go版本号的变化
以前,每个版本的第一个发行版的版本编号为"1.20"。从这个版本开始,第一个发行版的版本编号为"1.21.0"。
语言的变化
Go 1.21 新增了三个内置函数min、max、clear。其中clear用于删除掉一个map的所有键值对或者将一个slice的所有元素置为零值。
调整了包初始化顺序的算法。
进行了多项改进,提高了类型推断的能力和精度。
核心库
- 新增了
log/slog包 - 新增了
testing/slogtest包 - 新增了
slices包 - 新增了
maps包 - 新增了
cmp包 - 一些库的小变化,就不一一列举了
Go 1.20 新特性
切片转数组
现在支持将切片转为数组了,例如*(*[4]byte)(x)现在可以简写为[4]byte(x)。
unsafe包新增函数
unsafe包提供了三个新函数SliceData、String、StringData,这些函数现在提供了构造和解构切片和字符串值的完整功能。例如:
s := "abc"
buf := unsafe.Slice(unsafe.StringData(s), len(s))就可以得到字符串s的底层数组。
注意
但在一般情况下,你可以放心的使用[]byte(s)。如果编译器检测到后续不会再用到s,也会直接把它的底层数组返回出来,而不是复制一份。
关于comparable约束
a := map[string]any{"a": 1, "b": 2.3, "c": "c"}
b := map[string]any{"a": 1, "b": 2.3, "c": "c"}
fmt.Println(maps.Equal(a, b)) // 输出 true我们来看一下maps.Equal函数的定义:
func Equal[M1, M2 ~map[K]V, K, V comparable](m1 M1, m2 M2) bool在之前的版本,由于maps.Equal函数接收的两个map要求键与值都是comparable,但实际上它是map[string]any,any并不一定满足comparable,所以编译会报错。
在Go1.20之后,不会再因此而编译报错。如果出现不能比较的元素,则会在运行时报错:panic: runtime error: comparing uncomparable type []int
