加大力度!Go 将会增强 Go1 向后兼容性
大家好,我是煎鱼。
前段时间我们在写 Go1.20 新特性和变更时,发现了一个问题有悖论。
Go1 有兼容性承诺,但如果发现了 BUG,会破坏兼容性。那怎么办?是大胆修改,破坏掉,还是说设计如此,打死不改?
写了个开头结果阳了,现在阳康还咳嗽回来接着更。
Go1 兼容性保障
在 Go1 引入了 Go 兼容性保障《Go 1 and the Future of Go Programs[1]》,也就是旧版本的 Go 程序也可以在继续 Go 的新版本中正确运行。
当然,凡事有例外,像是安全问题就是例外。
具体的完整细则如下图:
我们常接触到的有以下几个:
安全问题:Go 规范或实施中的安全问题可能会被发现,其解决需要破坏兼容性。将会保留解决这些安全问题的权利。 未指定的行为:虽然 Go 规范中尝试明确所有已知行为,但是意料之外还是会存在一些方面是未定义的。这方面可能会出现问题。 规范错误:如果有必要解决规范(spec)中出现的不一致、不完整,将会保留解决此类问题的权利。除了安全问题,不会对规范进行不兼容的修改。 问题/缺陷:如果编译器、库有违反规范的缺陷,会保留修复这些缺陷的权利。 使用 import . 导入:如果在程序使用 import . "path"
,在未来的版本中,在导入的包中定义的其他名字可能会与程序中定义的其他名字相冲突。我们不推荐在测试之外使用 import .,使用它可能会导致程序在未来的版本中无法编译。引用 unsafe 库:导入 unsafe 的包可能依赖于 Go 实现的内部属性。会保留修改的权利。
Go 核心团队自述已经有 10+ 年的 Go1 兼容性保障的经验,对 Go 团队和用户来说都非常的有价值。
甚至近两年,Go 团队和业内把 Go 的高速发展归因于对 Go1 兼容性的保障的落地实施。
看起来还是有板有眼的。
扩展 Go 向后兼容性
背景
虽然主观上 Go 团队认为做的比较好,但发现仍然存在进行了兼容性破坏的情况。因此 Go 现任当家 @Russ Cox 发起了《extending Go backward compatibility[2]》。
其认为值得扩展 Go1 的向后兼容性,以尝试更少地破坏程序,明确地进行 GODEBUG 的设置,便于声明变更项在何时适应使用和控制。
简单来讲,就是 Go1 兼容性承诺给 Go 带来了非常大的好处,要继续扩大优势项,把长板拉长。
怎么突然提起
那为什么会突然想搞这事?因为 Russ Cox 最近和 Kubernetes 团队交流,发现在过去的几年里,Go 平均每年大约会有一个 Kubernetes 的破坏性变更。
其认为 Kubernetes 肯定不是一个个例。虽然每年 1 次左右的频率并不高,但 Go 团队在 Go1 兼容性的目标是是 0 次。
以下是对 Kubernetes 造成重大更改的一些示例:
有兴趣的同学可以细看,考虑大多数同学可能并不关心,所以我没有进一步展开。
现有与兼容性相关的 GODEBUG 设置包括如下:
GODEBUG=asyncpreemptoff=1
:禁用基于信号的 Goroutine 抢占,这偶尔会发现操作系统的错误。GODEBUG=cgocheck=0
:禁用运行时的 CGO 指针检查。GODEBUG=cpu.<extension>=off
:在运行时禁止使用某个特定的 CPU 扩展。GODEBUG=http2client=0
:禁用客户端的 HTTP/2。GODEBUG=http2server=0
:禁用服务器端的 HTTP/2。GODEBUG=netdns=cgo
:强制使用 CGO 解析器。GODEBUG=netdns=go
:强制使用 Go DNS 解析器。
扩大 Go1 兼容性保障
在新提案中,Go 将会正式确定并扩大对 GODEBUG 的使用,将根据 go.mod 中的 Go 版本号来设置对应 GODEBUG,以提供超越当前兼容性准则所保证的兼容性。
也就是接下来将会延伸以往的 GODEBUG 配置项,扩大使用面。
新措施的具体内容如下:
承诺始终为兼容性指南允许的更改添加 GODEBUG 设置,但这仍然可能会破坏大量实际程序。 保证 GODEBUG 设置至少持续 2 年(4 个版本)。这只是最低要求;会存在例如,例如:http2server,可能会永远存在。 提供运行时/指标计数器,可用于观察由 GODEBUG 设置导致的非默认行为。如: /godebug/non-default-behavior/<name>:events
。根据 Go modules 主模块的 go.mod 中的 Go 版本,给 Go 应用设置对应的 GODEBUG 设置。注意不是当前编译的 Go 版本。是根据 go.mod 内的 Go 版本号。 允许使用以下形式的一行或多行覆盖主包源代码中的特定默认 GODEBUG 设置: //go:debug <name>=<value>
。会同步修改 go/build、go list、go version -m 等配套工具链的使用,确保 GODEBUG 设置能够被显式查看。 在兼容性指南中记录这些承诺以及如何配置使用 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 在这几年对 Go1 兼容性保障越来越看重,在今年将会进一步加强。该提案已经到了最终阶段,很有可能会被接受,且最新评论没有反对意见。
该提案将会加大在兼容性上 GODEBUG 的应用,且最重要的是,将会根据 go.mod 文件中的 Go 版本来调整 GODEBUG,这会是一个重大微调整。
唯一纠结的同学,主要是反馈很多 Go 开发者,不知道自己修改 go.mod 文件中的 go 版本时,会导致 GODEBUG 的变更,从而影响到程序,会比较隐晦。
想当年,rsc 给 go.mod 加 go 版本号时,表示还没想好用在哪里...我只想表示这棵树也埋的真深。
参考资料
Go 1 and the Future of Go Programs: https://go.dev/doc/go1compat
[2]extending Go backward compatibility: https://github.com/golang/go/discussions/55090
关注和加煎鱼微信,
一手消息和知识,拉你进技术交流群👇
你好,我是煎鱼,出版过 Go 畅销书《Go 语言编程之旅》,再到获得 GOP(Go 领域最有观点专家)荣誉,点击蓝字查看我的出书之路。
日常分享高质量文章,输出 Go 面试、工作经验、架构设计,加微信拉读者交流群,和大家交流!