起因
最近 Go 发布了 1.26 版本,我打算升级体验新特性。在阅读 Go 1.26 Release Notes 时,发现了一个值得注意的变化:go mod init 命令会默认使用较低版本的工具链。
官方意图
Go 1.26 Release Notes 中有如下描述:
go mod init now defaults to a lower go version in new go.mod files. Running go mod init using a toolchain of version 1.N.X will create a go.mod file specifying the Go version go 1.(N-1).0. Pre-release versions of 1.N will create go.mod files specifying go 1.(N-2).0. For example, the Go 1.26 release candidates will create go.mod files with go 1.24.0, and Go 1.26 and its minor releases will create go.mod files with go 1.25.0. This is intended to encourage the creation of modules that are compatible with currently supported versions of Go. For additional control over the go version in new modules, go mod init can be followed up with go get go@version.
简单翻译:
使用 Go 1.N.x 工具链执行 go mod init 时,生成的 go.mod 文件中会写入一个较低的 Go 版本:正式版会写入 go 1.(N-1).0,预发布版(如 RC)会写入 go 1.(N-2).0。例如,Go 1.26 的 RC 版本会生成 go 1.24.0,而 Go 1.26.0 及后续补丁版本会生成 go 1.25.0。官方此举意在鼓励开发者创建与当前受支持 Go 版本相兼容的模块。如需自定义版本,可以在 go mod init 后执行 go get go@version 来调整。
第一眼看到这个设计,直觉上就感到困惑:既然我已经选择了新版本工具链,为什么默认要使用旧版本构建?🤔
深入调查
带着疑问,我前往 Go 的 GitHub 仓库寻找答案。这个变化来自 issue #74748(cmd/go: change go mod init default go directive to 1.(N-n).0)。该提案并未经过社区广泛讨论,就被官方快速采纳并合入 1.26 版本。
然而反转很快出现。在 issue #77653 中,用户 willfaught 指出了这一变化带来的实际问题:在 1.26 版本下,使用 go run 可以正常使用 new(42) 这类新语法特性,但一旦执行 go mod init 再 go build,就会构建失败。这极其反直觉。
问题复现
根据 #77653 中的代码示例进行复现:
测试代码
❯ cat main.go
package main
import "fmt"
func main() {
fmt.Println(new(42))
}
❯ go run main.go
0x4a524480c0b0
Go 1.26 的行为
❯ go mod init t
go: creating new go.mod: module t
go: to add module requirements and sums:
go mod tidy
❯ go build
./main.go:6:14: new(42) requires go1.26 or later (-lang was set to go1.25; check go.mod)
可见,尽管工具链是 1.26,生成的 go.mod 却写入了 go 1.25,导致构建时编译器认为语言版本过低,拒绝了新语法。
Go 1.26.2 的行为
❯ go mod init t
go: creating new go.mod: module t
go: to add module requirements and sums:
go mod tidy
❯ go build
❯ ./t
0x7eb4aed94020
官方在 Go 1.26.1 就已经回退了这个改动
个人观点
个人建议避免使用 Go 1.26 这个版本。它的 go mod init 降级行为会导致构建产物出现预期之外的问题,而且后续从 1.26迁移到新版本时也可能产生不一致的执行结果。
官方的初衷是为了兼容性——鼓励模块兼容当前受支持的旧版 Go。但讽刺的是,这个改动本身却制造了严重的兼容性问题:用户使用新工具链初始化项目,却无法用新特性构建。
我认为,是否降低默认工具链版本应该由开发者自己决定,而不是官方一刀切地强制降级。这种可能导致特性不兼容的决策,理应由开发者根据实际需求来把控。
对于初学者而言,go mod init 之后还要手动修改 go.mod 版本,是一个相当隐蔽的坑。
结论与建议
- Go 1.26 存在
go mod init 的版本降级问题,会导致使用新语法特性的项目无法通过 go build。
- 官方已在 Go 1.26.1 中回退该行为,1.26.2 及更高版本均可正常使用。
- 建议升级到 Go 1.26.1 或更高版本,避免踩坑。
参考文档
起因
最近 Go 发布了 1.26 版本,我打算升级体验新特性。在阅读 Go 1.26 Release Notes 时,发现了一个值得注意的变化:
go mod init命令会默认使用较低版本的工具链。官方意图
Go 1.26 Release Notes 中有如下描述:
简单翻译:
使用 Go 1.N.x 工具链执行
go mod init时,生成的go.mod文件中会写入一个较低的 Go 版本:正式版会写入go 1.(N-1).0,预发布版(如 RC)会写入go 1.(N-2).0。例如,Go 1.26 的 RC 版本会生成go 1.24.0,而 Go 1.26.0 及后续补丁版本会生成go 1.25.0。官方此举意在鼓励开发者创建与当前受支持 Go 版本相兼容的模块。如需自定义版本,可以在go mod init后执行go get go@version来调整。第一眼看到这个设计,直觉上就感到困惑:既然我已经选择了新版本工具链,为什么默认要使用旧版本构建?🤔
深入调查
带着疑问,我前往 Go 的 GitHub 仓库寻找答案。这个变化来自 issue #74748(
cmd/go: change go mod init default go directive to 1.(N-n).0)。该提案并未经过社区广泛讨论,就被官方快速采纳并合入 1.26 版本。然而反转很快出现。在 issue #77653 中,用户 willfaught 指出了这一变化带来的实际问题:在 1.26 版本下,使用
go run可以正常使用new(42)这类新语法特性,但一旦执行go mod init再go build,就会构建失败。这极其反直觉。问题复现
根据 #77653 中的代码示例进行复现:
测试代码
Go 1.26 的行为
❯ go mod init t go: creating new go.mod: module t go: to add module requirements and sums: go mod tidy ❯ go build ./main.go:6:14: new(42) requires go1.26 or later (-lang was set to go1.25; check go.mod)可见,尽管工具链是 1.26,生成的
go.mod却写入了go 1.25,导致构建时编译器认为语言版本过低,拒绝了新语法。Go 1.26.2 的行为
❯ go mod init t go: creating new go.mod: module t go: to add module requirements and sums: go mod tidy ❯ go build ❯ ./t 0x7eb4aed94020官方在 Go 1.26.1 就已经回退了这个改动
个人观点
个人建议避免使用 Go 1.26 这个版本。它的
go mod init降级行为会导致构建产物出现预期之外的问题,而且后续从 1.26迁移到新版本时也可能产生不一致的执行结果。官方的初衷是为了兼容性——鼓励模块兼容当前受支持的旧版 Go。但讽刺的是,这个改动本身却制造了严重的兼容性问题:用户使用新工具链初始化项目,却无法用新特性构建。
我认为,是否降低默认工具链版本应该由开发者自己决定,而不是官方一刀切地强制降级。这种可能导致特性不兼容的决策,理应由开发者根据实际需求来把控。
对于初学者而言,
go mod init之后还要手动修改go.mod版本,是一个相当隐蔽的坑。结论与建议
go mod init的版本降级问题,会导致使用新语法特性的项目无法通过go build。参考文档