2026 年 2 月,Go 1.26 悄悄改了 go mod init 的默认行为。
用 Go 1.26 初始化一个新项目,go.mod 里的版本号不再是 go 1.26,而是 go 1.25——整整落后了一个版本。
$ go version # go1.26
$ go mod init myapp
$ cat go.mod # go 1.25 ← 等等,这不对吧?
初看是小事,但问题随之而来。
来看一个具体场景。这是一段用了 Go 1.26 新语法的代码:
package main
import "fmt"
func main() {
fmt.Println(new(42)) // Go 1.26 的新语法
}
直接运行,完全正常:
$ go run t.go # 没有任何问题
然后初始化一个模块,再构建:
$ go mod init t
$ go build
# ./t.go:6:14: new(42) requires go1.26 or later
# (-lang was set to go1.25; check go.mod) # 报错了
相同的代码、相同的机器、相同的工具链——只是多了一个 go.mod,构建就炸了。
这正是 GitHub issue #77653 描述的问题,标题直白:「改回 go mod init 默认使用 1.N」。issue 创建后数小时内,社区支持如潮水般涌来。

这个改动来自 #74748,官方理由有一定道理:
"默认使用 1.(N-1).0 可以给用户更好的初始值,特别是那些不会手动修改
go指令的用户。" "根据 Go 发布政策,我们支持最近两个主要版本,所以这不会切断任何当前受支持的工具链。"
翻成人话:如果你发布一个模块,默认要求 go 1.25 比要求 go 1.26 更「友好」——还在用 Go 1.25 的开发者可以直接用你的模块,不用升级。
Go 核心开发者 @mvdan 支持这个改动,理由也坦诚:
"我日常跑 tip 版本,几乎每次
go mod init之后都要手动改go.mod。"
这逻辑没问题。但它暴露了一个视角盲点。
#77653 的反对意见集中在三点。
第一:违反直觉
@seankhliao 一针见血:
"语言特性和兼容性设置都由
go指令控制。如果我安装了最新工具链,却默认不能用最新特性,还得手动更新版本,这是个更差的默认值。"
我装 Go 1.26,就是为了用 Go 1.26 的功能。这是最基本的直觉,工具链应当匹配它,而不是悄悄绕开它。
第二:问题没解决,只是偏移了
原 issue 作者指出了更深层的矛盾:
"默认版本仍然每个发布周期都会涨,只是落后一个版本。问题没解决,只是移动了一小步。"
这个「保守默认」的有效期只有 6 个月——等 Go 1.27 发布,你「保守」的 go 1.25 模块就已经落后两个版本了。用转瞬即逝的兼容性优势,换来数百万开发者的持续困惑,这笔账怎么算都亏。
第三:伤害最大的是新手
@dolmen 的担忧最有分量:
"对初学者不好。他们不知道为什么安装了最新 Go,却用不了最新特性。"
有经验的开发者踩坑了,搜一下,五分钟解决。新手可能会怀疑是不是自己装错了,是不是操作系统有问题,是不是代码写错了。这种隐性摩擦,对老手不可见,对新手代价高昂。
issue 作者的这句话是整个讨论里最有力的一句:
"为什么要让数百万普通用户承受困惑,来换取 20-50 个 Go 团队成员的便利?"
go 指令到底控制什么这场争论也让很多人意识到,go.mod 里那行 go 1.xx 远不只是个标签,它同时控制三件事:
new(42)、iter.Pull 等特性是否合法
Go 1.26 的改动,本质上是把「最低工具链要求」和「可用的语言特性」强行解耦。初衷是让模块更易被引用,实际效果是:你必须显式告诉工具链「我想用我安装的这个版本」。
还有一个讽刺之处:Go 的工具链已经支持前向兼容——如果你的 Go 版本低于模块要求,它会自动下载合适的版本来构建。工具链本身早就够聪明了。 人为压低默认版本,不带来持久的兼容性收益,只在项目初始化时增加了一个必须记住的额外步骤。
在这个 issue 有最终结论之前,临时解决方案很简单:
# 初始化后立即更新 go 指令
go mod init myapp && go mod edit -go=1.26
# 或者手动编辑 go.mod
# 将 go 1.25 改为 go 1.26
如果你写的是库,真的需要支持 Go 1.25 用户,那显式设置 go 1.25 是合理的。关键在于,这应该是你的主动选择,而不是工具替你做出的默认决定。
这件事揭示了工具设计里的经典矛盾:以生态为先的设计,可能与以用户体验为先的直觉相悖。
视角 | 关注点 | 倾向 |
|---|---|---|
Go 团队 | 模块生态兼容性 | 保守默认 |
普通开发者 | 工具链体验一致性 | 版本应当匹配 |
模块消费者 | 不想被迫升级 | 越低越好 |
作为一个写了多年 Go 的开发者,我理解 Go 团队保护生态的出发点。但好的默认值应该是无感的——它匹配用户预期,不需要解释,不需要文档,不需要记忆。
一个让数百万开发者必须记住「初始化后要额外跑一条命令」的默认值,不是保守,是设计不佳。
参考链接: