大家好,我是煎鱼。
上年我给大家分享过《加大力度!Go 将会增强 Go1 向后兼容性》,当时是 Go 核心团队负责人 @Russ Cox(下称:rsc)主导和推进的。
没想到,那么快就发现新的坑。为此 rsc 光速推进了一个新的提案《cmd/go: separate default GODEBUGs from go language version》,现在已经是接受状态了。
今天就由本煎鱼为大家分享和说明具体的内容和缘由。
快速背景
当时提案的核心目的是:为了加强 Go 向后兼容性,具体的行为是:根据 go.mod 中的 Go 版本号来设置对应 GODEBUG,以提供超越当前兼容性准则所保证的兼容性。
使用案例来看,跟现有的 GODEBUG 其实是类似。例如 Go1.20 时引入了一个新的 GODEBUG zipinsecurepath。
该值会遵循一定的流程规范,当时官方给出的例子如下:
- Go1.20 中默认值为 1,以保留旧的行为并允许不安全的路径。
- Go1.21 会将默认值更改为 0,以开始拒绝 archive/zip 中的不安全路径。如果是这样,且 Go1.21 也实现了这个 GODEBUG 提案,那么当使用 Go1.21 编译的带有 Go1.20 的模块(go.mod)时,将继续允许不安全的路径。只有当这些模块版本更新到 Go1.21 时,它们才会开始拒绝不安全的路径。
以上是当时定的提案方向和实施的结论。看着很美好,不过咱们程序员写代码,总会有一些意料之外的场景导致新 “坑” 出现。
兼容规则小结
我们对现在 Go 处理兼容性的方式做一下规则总结,如下:
定义了名为 GODEBUG 设置的配置方式,让用户可以控制这些更改是否或何时在其特定程序中发生。
- 默认设置取自主软件包 go.mod 文件中的 "go" 版本行。
- 主软件包 Go 源文件可以用
//go:debug key=value
行做覆盖。
- 对于 Go Toolchain(工具链),决定当前工具链版本是否足够新以此满足构建模块的要求时,需要检查顶级的 “go” 行,以其为准。
- 任何模块中的 "go" 行必须是其依赖关系中所有 "go" 行的最大值。
翻车在哪
仅仅经过了 1.5 个大版本,Go 社区就发现了新的问题。这几个方面的机制产生了冲突,以一种令人遗憾的方式相互影响。
具体场景如下:
1、某个特定程序的维护版本 M 希望锁定从他们开始使用的发布版中的 GODEBUG 语义,他们可以通过设置“go”行来实现,即使在更新到更新的工具链时也可以。
2、但是当他们更新到依赖项的更新版本时,如果这些依赖项已经更新到更新的 “go” 行,那么这将迫使在顶层模块中使用更新的 “go” 行,从而更改了默认的 GODEBUG 设置。
3、如果这种情况发生在一个依赖项上,它可以被 fork+replace。但是对于具有庞大依赖树的模块项目,这可能会发生在许多依赖项上,此时 fork+replace 就不再具备可扩展性。
解决方案
变更方向
Go 官方将计划做两处修改,以便将原先默认的 GODEBUG
与 go.mod
中的 "go" 行分离,解决上述的兼容规则冲突的问题。
添加一个新的基础设置:
default=go1.X
,作用和含义是:"按照 Go 1.X 的方式设置一切",与 "go 1.X" 行的意思相同。- 对于这个新的基础设置而言,
go.mod
文件中的 "go 1.X" 行实质上意味着每个主程序包中的//go:debug default=go1.X
。 - 该设置也可以在
$GODEBUG
中使用。 - 你可以有一个写着 "go 1.23" 的模块,但主程序却写着
//go:debug default=go1.21
。
- 对于这个新的基础设置而言,
- 在
go.mod
和go.work
中添加一行新的godebug
,仅在当前模块中使用(就像 toolchain、replace 和 exclude 仅在当前模块中使用一样)。它需要一个k=v
参数,与//go:debug
源代码行相同,但适用于模块中的所有软件包 main。
对我们影响最大的核心点之一是:go.mod 文件中的 go 行不再和 GODEBUG 强绑定,拆了一个新的 godebug 语义和用法出来,两者独立开来,不再在兼容性机制上重叠。
具体例子
基于上述的变更说明,godebug default
的案例,代码如下:
go 1.23
godebug default=go1.21
也可以设置多种 godebug,go.mod 或 go.work 均是一样的表达方式。
代码如下:
go 1.23
godebug (
default=go1.21
panicnil=0
)
优先顺序如下:运行时的 $GODEBUG
> 软件包 main 中的 //go:debug
行 > go.work
或 go.mod
(使用工作区时为 go.work,否则为 go.mod)
一些瓜
现阶段这个提案在 rsc 的高度关注下,快速穿越过各个提案阶段,已经 Accepted。根据我的观察,本次问题,大概率是 Kubernetes 团队在 Google 发现和在内部提出的。
因为 @Jordan Liggitt 光速出现在了评论区,也符合具有庞大依赖树的模块项目的特征,同时也强关注了本提案。
果然,无论是哪里。对于大客户和自家大佬的问题和进度都是特别关注和高资源推进的。(有内部渠道光速响应)
总结
本提案确实解决了前一轮 Go1 兼容性保障增强带来的副作用,共用预定义的泛化内容总是会带来一些奇奇怪怪的问题。本次拆开也便于直观上的理解。
也可以满足使用更新的 Go 版本,且同时 GODEBUG 语义使用更低版本的诉求,这我觉得是不错的。这样未来 Go 新版本好的优化也可以享受到。
根据提案内容和计划,预计本次变更最快可以在 Go1.23 享受到。大家可以期待一下!
文章持续更新,可以微信搜【脑子进煎鱼了】阅读,本文 GitHub github.com/eddycjy/blog 已收录,学习 Go 语言可以看 Go 学习地图和路线,欢迎 Star 催更。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。