Go语言版本新特性总结
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