Go各版本Release Note
Go1.21.0
2023-08-08
https://go.dev/doc/go1.21
内置方法
min & max:返回一个序列中的最大值最小值。
https://go.dev/ref/spec#Min_and_max
clear:清空map和slice。
https://go.dev/ref/spec#Clear
标准库
log/slog
The new log/slog package provides structured logging with levels. Structured logging emits key-value pairs to enable fast, accurate processing of large amounts of log data. The package supports integration with popular log analysis tools and services.
testing/slogtest
The new testing/slogtest package can help to validate slog.Handler implementations.
slices
The new slices package provides many common operations on slices, using generic functions that work with slices of any element type.
maps
The new maps package provides several common operations on maps, using generic functions that work with maps of any key or element type.
cmp
The new cmp package defines the type constraint Ordered
and two new generic functions Less
and Compare
that are useful with ordered types.
toolchain
Go工具链管理,以及Go版本匹配规则。下文说到。
Go1.22
2024-02-06
1、修复 for 循环变量的错误
在此之前的写法
func main() {
for i := 0; i < 5; i++ {
v := i
wg.Add(1)
go func() {
defer wg.Done()
fmt.Println(v)
}()
}
wg.Wait()
}
现在的写法
func main() {
for i := 0; i < 5; i++ {
wg.Add(1)
go func() {
defer wg.Done()
fmt.Println(i)
}()
}
wg.Wait()
}
2、range可以遍历整数
for i := range 10 {
fmt.Println(10 - i)
}
3、标准库
math/rand/v2
https://go.dev/pkg/math/rand/v2/
go/version
The new go/version
package implements functions for validating and comparing Go version strings.
Go1.23
2024-08-13
for-range 循环中的范围表达式现在可以是迭代器函数,例如 func(func(K) bool),这支持了用户对任意序列自定义迭代器。同时,slices 和 maps 包新增了一些与迭代器配合使用的功能,还新增了一个 iter 包。比如可以轻松地将 map 的键收集到切片中并对其值进行排序。
Range Over Function Types
标准库
1、对 time.Timer 和 time.Ticker 做了重大改动,与老的写法不兼容,鉴于Go的工具链管理,升级之后并不会对之前的项目构成影响,前提是你依旧按照原先的版本编译。
https://go.dev/doc/go1.23#library
首先,程序不再引用的计时器和 Tickers 立即有资格使用垃圾回收机制,即使它们的 Stop 方法没有被调用。早期版本的 Go 直到发射后才收集未停止的计时器,并且从未收集未停止的 Tickers。
其次,与 Timer 或 Ticker 关联的计时器通道现在是无缓冲的,容量为 0。此更改的主要效果是 Go 现在保证对于对 Reset 或 Stop 方法的任何调用,不会在调用后发送或接收在该调用之前准备的陈旧值。Go 的早期版本使用带有单元素缓冲区的通道,因此很难正确使用 Reset 和 Stop。此更改的一个明显效果是计时器通道的 len 和 Cap 现在返回 0 而不是 1,这可能会影响轮询长度以决定计时器通道上的接收是否会成功的程序。此类代码应使用非阻塞接收代替。
特别说明,当你的go.mod的go行大宇等于1.23.0,此项特性才会生效。当你用Go1.23编译老代码时,此特性不会生效。
2、增加了 unique 包
3、Iterators包
4、structs包
The new structs
package provides types for struct fields that modify properties of the containing struct type such as memory layout.
向前兼容性
在以前的版本中,Go 工具链尝试编译依赖于新版本的代码时,可能会遇到兼容性问题。例如,如果你的代码依赖于 Go 1.18 引入的新特性,使用小于这个本本版本的 Go 编译器将会导致编译错误。但是,这些错误信息有时并不直观,可能仅仅显示为语法错误,而实际上是由于工具链版本不匹配。
在 Go 1.21 中,工具链会严格遵循 go.mod
文件中的版本声明。例如,如果你的 go.mod
文件中声明了 go 1.21.1
,那么 Go 1.21.0 将无法编译这段代码。这种方式有效地避免了潜在的编译错误。
工具链管理
为了减少强制版本匹配
对开发者的影响,Go 1.21 还引入了工具链管理功能,使得你可以为不同的项目指定不同的工具链版本。这一功能类似于 Node 的 nvm
或 Rust 的 rustup
,但它是内置于 Go 的核心工具中。
// go.mod 文件
module example
go 1.21.0
toolchain go1.21.4
在上面的配置中,toolchain go1.21.4
指定了在当前模块中需要使用 Go 1.21.4 工具链。Go 命令会自动下载并使用指定的工具链版本,而无需手动干预。
更新 go.mod 中的 go 行:
go get go@1.21.0
,该命令将下载并切换到 Go 1.21.0 版本,并自动更新 go
行。
如果你希望在保持旧版本 go
行不变的情况下更新工具链,可以使用:go get toolchain@go1.21.0
。工具链就是run/build/link等等工具。
强制使用特定工具链版本
你可以通过 GOTOOLCHAIN
环境变量来强制使用特定版本的 Go 工具链。
关于环境变量,分为临时的(会话级别的)和永久生效的,优先级临时>永久
.
# 查看
go env GOTOOLCHAIN
# 设置永久生效(不推荐)
go env -w GOTOOLCHAIN=go1.20
# windows系统,设置临时
set GOTOOLCHAIN=go1.20
# Linux系统,直接放在命令开头即可
# windows系统不支持这种写法
GOTOOLCHAIN=go1.20 go run main.go
可以理解为在 Go 1.21 以前,我们用的是GOTOOLCHAIN=local
,即使用你本地安装的Go版本。
在 Go 1.21 之后,如果Go发现本地工具链版本低于go mod要求的最低版本,那么Go会自动下载匹配的Go工具链,缓存到go module cache(不会覆盖本地安装的go工具链),保存在 pkg/mod 目录下,就像你下载普通的依赖一样,并用新下载的Go工具链对module进行编译构建。
也就是说,你本地现在可以有多个版本的Go工具链,主版本就是你的安装版本,其他的版本就是用来编译。
有时候你可能有多个项目,它们依赖不同的Go版本。
go xxx 就是初始化项目的时候你本地安装的Go版本。
toolchain goxxx 是编译时要求使用的Go版本,出现这种情况,可能是依赖的第三方包要求的Go版本高于你本地安装的版本。
这就是所谓的向前兼容性,开发者可以放心使用新语言特性,无需担心旧版本编译器带来的问题,go命令会自动处理这一切
自动升级工具链
你还可以设置自动升级工具链版本。使用 GOTOOLCHAIN
环境变量的形式 version+auto
可以在保留当前版本的同时允许自动升级。例如:go env -w GOTOOLCHAIN=go1.21.1+auto
当 Go 1.21.1 发布时,你的系统将自动使用该版本。
Go 1.21 的工具链管理功能和向前兼容性改进将大大提升 Go 开发的便利性。通过这些新特性,你可以更好地管理不同版本的工具链,确保代码在不同版本的 Go 中稳定运行。
那么你如何知道Go到底会使用哪个版本编译呢?
方法1:go version
在 go env 的输出中,如果 GOTOOLCHAIN=auto,那么 GOVERSION 的值一般就是安装的Go版本。
终端1:
go env GOTOOLCHAIN
auto
go env GOVERSION
go1.23.4
go version
go version go1.23.4 windows/amd64
如果设置 GOTOOLCHAIN=go1.20,那么 GOTOOLCHAIN 和 GOVERSION 的值就是一样的。
终端2:
# 设置临时环境变量,只在当前命令行生效
set GOTOOLCHAIN=go1.20
go env GOTOOLCHAIN
go1.20
go env GOVERSION
go1.20
go version
go version go1.20 windows/amd64
go version的值就是 go env GOVERSION的值,也就是说 go version 的确可以预示着会使用哪个版本的Go编译此项目。但是 go version 只是检查到了环境变量这一层,并不会检查go.mod中的toolchain行。
方法2:-x 参数
go run -x main.go
go build -x main.go
会打印出执行过程,能看到使用的是哪个工具链。
终端1:
go run -x main.go
mkdir -p $WORK\b001\exe\
cd .
GOROOT='D:\Go' "D:\\Go\\pkg\\tool\\windows_amd64\\link.exe" -o "$WORK\\b001\\exe\\main.exe".......
终端2:
set GOTOOLCHAIN=go1.20
go run -x main.go
mkdir -p $WORK\b001\exe\
cd .
"D:\\dev\\php\\magook\\trunk\\server\\golang\\path\\pkg\\mod\\golang.org\\toolchain@v0.0.1-go1.20.windows-amd64\\pkg\\tool\\windows_amd64\\link.exe" -o "$WORK\\b001\\exe\\main.exe".......
从结果来看的确是使用了不同版本的工具。
多个版本的Go被保存在PATH/pkg/mod/golang.org/toolchain
@v0.0.1-goxxxxx。
Go命令选择版本的规则是什么?
toolchain 行一定要大于等于 go1.21.0,否则会报错,然后 toolchain 行可以大于 go 行,也可以小于 go 行,不会报错。
Go命令会先比较 toolchain 行和 go 行,取大的,然后与本地安装版本比较,取大的,如果结果大于本地版本,才会下载使用新的版本,否则只会使用本地版本,Go保证高版本可以正常编译低版本。
如果你引入的第三方包要求的版本大于go.mod中的go行,且大于本地安装版本,会自动为你加上 toolchain goxxx
。
在 go1.21.0 之后,go 行,toolchain 行,GOTOOLCHAIN都要精确到三级版本号,否则会报错。
升级到Go1.23.4
把Go升级到大于等于1.21.0已经是不得不做的事情,因为未来很多第三方包会依赖于1.21.0及其以上,而你的项目将无法使用它们,但是这又不是绝对的,在1.20的时候我的项目依赖了1.21.4的包,windows平台可以正常编译,但是darwin则无法编译。
windows系统直接下载msi安装包安装即可。
Linux下载tar.gz直接覆盖即可。
如果不想覆盖只想体验下新版本:go install golang.org/dl/go1.23.0@latest
只会下载Go的可执行文件到 $GOPATH/bin/go1.23.4.exe
,并不算版本更新。
Windows系统设置环境变量
GOSUMDB
需要设置,因为如果触发了下载其他版本,就进行校验,如果目标地址设置错误就会报错
go: downloading go1.20 (linux/amd64)
go: download go1.20: golang.org/toolchain@v0.0.1-go1.20.linux-amd64: verifying module: invalid GOSUMDB: malformed verifier id
因为我的项目大多在1.20版本,所以我需要测试一下有没有问题。
一个简单的测试项目go-new
util
util.go
go.mod
main.go
// go.mod
module go-new
go 1.20
// main.go
package main
import (
"fmt"
"go-new/util"
)
func main() {
fmt.Println(util.Max(1, 2))
}
set GOTOOLCHAIN=go1.20
go run main.go
main.go:5:2: ambiguous import: found package go-new/util in multiple modules:
go-new (D:\dev\php\magook\trunk\server\go-new\util)
(D:\dev\php\magook\trunk\server\golang\path\pkg\mod\golang.org\toolchain@v0.0.1-go1.20.windows-amd64\src\go-new\util)
set GOTOOLCHAIN=go1.19
go run main.go
main.go:5:2: cannot find package "go-new/util" in:
D:\dev\php\magook\trunk\server\golang\path\pkg\mod\golang.org\toolchain@v0.0.1-go1.19.windows-amd64\src\go-new\util
set GOTOOLCHAIN=go1.18
go run main.go
可以正常运行
修改 go.mod
module go-new
go 1.21.0
set GOTOOLCHAIN=go1.21.0
go run main.go
可以正常运行
set GOTOOLCHAIN=go1.22.0
go run main.go
可以正常运行
由此可见,GOTOOLCHAIN 在低于 1.21.0的时候存在兼容性问题,这意味着,当我升级到Go1.23.4的时候,我过往的项目(依赖于Go1.20及其以下)应该在Go1.21.0版本运行,但是,如果你信任Go官方的话,这些都不需要。
如果我一直停留在Go1.23.4,随着时间的推移,有些第三方包会依赖更高版本的Go,到时候可以用go.mod中的toolchain行来解决,这个会自动被添加上的,不需要操心。
阅读Go的release note可以发现Go基本是保证向前兼容,其新特性是以增量的方式加入的,而不会改变以前版本的特性,但并不代表你可以不关心了,比如上面提到的在Go1.23.0中的time.Timer的特性改动,如果你升级到了1.23但是并不了解其变化而依旧使用之前的方式来编码,结果可能会超出你的认知,导致严重的后果。
在 Go 1.21 之后我们要分清楚几个概念
- go命令,它与Go版本无关(或淡化),就是个命令。
- go.mod 中的 go 行,是当前模块开发时的版本要求。
- go.mod 中的 toolchain 行,是依赖的第三方包的版本要求。
- 不同版本的Go会像普通的依赖包一样同时存在。
总结
说了那么多,升级到新版本后,我该注意些什么呢?答案是什么也不用。
基于Go的新版本都是增量发布的,不会改变旧的逻辑,即便是不得不大改的time.Timer,Go也会根据go.mod中go行来区别对待,所以我们不要改动go行。放心的使用Go1.23.4来编译Go1.20的项目。
参考
Forward Compatibility and Toolchain Management in Go 1.21
Go Toolchains