Go 1.19 发行说明
Go 1.19 简介
最新的 Go 版本 1.19,在 Go 1.18 发布五个月后到来。它的主要变化集中在工具链、运行时和库的实现上。和往常一样,此次发布保持了 Go 1 兼容性承诺。我们预计几乎所有 Go 程序都能像以前一样继续编译和运行。
语言变化
语言方面只有一个小改动,即对方法声明中类型参数的范围进行了非常小的修正。现有程序不受影响。
内存模型
Go 内存模型已修订,以使 Go 与 C、C++、Java、JavaScript、Rust 和 Swift 使用的内存模型保持一致。Go 只提供顺序一致的原子操作,而不提供其他语言中更宽松的形式。随着内存模型的更新,Go 1.19 在 sync/atomic 包中引入了新类型,使原子值更容易使用,例如 atomic.Int64 和 atomic.Pointer[T]。
移植
LoongArch 64 位
Go 1.19 添加了对 Linux 上龙芯 64 位架构 LoongArch 的支持(GOOS=linux,GOARCH=loong64)。实现的 ABI 是 LP64D。支持的最低内核版本是 5.19。
请注意,大多数现有的商用 LoongArch Linux 发行版都带有较旧的内核,其系统调用 ABI 在历史上不兼容。编译的二进制文件在这些系统上将无法工作,即使是静态链接的也不行。此类不受支持系统上的用户仅限于使用发行版提供的 Go 包。
RISC-V
riscv64 端口现在支持使用寄存器传递函数参数和结果。基准测试显示 riscv64 上的典型性能提升达到 10% 或更多。
工具
文档注释
Go 1.19 添加了对文档注释中链接、列表和更清晰标题的支持。作为此更改的一部分,gofmt 现在会重新格式化文档注释,使其呈现的含义更清晰。有关语法详细信息和 gofmt 现在突出显示常见错误的描述,请参阅“Go 文档注释”。作为此更改的另一部分,新包 go/doc/comment 提供文档注释的解析和重新格式化,以及支持将其渲染为 HTML、Markdown 和文本。
新的 unix 构建约束
//go:build 行现在可以识别构建约束 unix。如果目标操作系统(也称为 GOOS)是 Unix 或类 Unix 系统,则该约束满足。对于 1.19 版本,如果 GOOS 是 aix、android、darwin、dragonfly、freebsd、hurd、illumos、ios、linux、netbsd、openbsd 或 solaris 之一,则该约束满足。在未来的版本中,unix 约束可能会匹配更多新支持的操作系统。
Go 命令
-trimpath 标志(如果设置)现在包含在 go build 注入 Go 二进制文件的构建设置中,可以使用 go version -m 或 debug.ReadBuildInfo 进行检查。
go generate 现在在生成器的环境中明确设置 GOROOT 环境变量,以便生成器即使在使用 -trimpath 构建时也能找到正确的 GOROOT。
go test 和 go generate 现在将 GOROOT/bin 放在用于子进程的 PATH 的开头,因此执行 go 命令的测试和生成器会将其解析到相同的 GOROOT。
go env 现在会引用其报告的 CGO_CFLAGS、CGO_CPPFLAGS、CGO_CXXFLAGS、CGO_FFLAGS、CGO_LDFLAGS 和 GOGCCFLAGS 变量中包含空格的条目。
go list -json 现在接受以逗号分隔的 JSON 字段列表来填充。如果指定了列表,则 JSON 输出将仅包含这些字段,并且 go list 可能会避免计算未包含的字段。在某些情况下,这可能会抑制原本会报告的错误。
go 命令现在缓存加载某些模块所需的信息,这应该会加快一些 go list 的调用速度。
Vet
vet 检查器“errorsas”现在报告当 errors.As 以 *error 类型的第二个参数调用时(一个常见的错误)。
运行时
运行时现在支持软内存限制。此内存限制包括 Go 堆和运行时管理的所有其他内存,不包括外部内存源,例如二进制文件本身的映射、其他语言管理的内存以及操作系统为 Go 程序持有的内存。此限制可以通过 runtime/debug.SetMemoryLimit 或等效的 GOMEMLIMIT 环境变量进行管理。该限制与 runtime/debug.SetGCPercent / GOGC 协同工作,即使 GOGC=off 也会被遵守,从而允许 Go 程序始终最大化地利用其内存限制,在某些情况下提高资源效率。有关更详细的软内存限制以及各种常见用例和场景的详细指南,请参阅 GC 指南。请注意,由于外部延迟因素(例如操作系统调度),较小的内存限制(例如几十兆字节或更少)不太可能被遵守。有关更多详细信息,请参阅 issue 52433。较大的内存限制(例如几百兆字节或更多)是稳定的并且已准备好投入生产。
为了限制程序实时堆大小接近软内存限制时 GC 抖动的影响,Go 运行时还尝试将 GC CPU 总利用率限制在 50%(不包括空闲时间),选择使用更多内存而不是阻止应用程序进程。实际上,我们预计此限制仅在特殊情况下发挥作用,新的 运行时指标 /gc/limiter/last-enabled:gc-cycle 报告上次发生此情况的时间。
当应用程序空闲到足以强制进行周期性 GC 循环时,运行时现在会在空闲操作系统线程上调度更少的 GC 工作 goroutine。
运行时现在将根据 goroutine 历史平均堆栈使用情况分配初始 goroutine 堆栈。这避免了平均情况下一些早期堆栈增长和复制的需求,但会为低于平均水平的 goroutine 浪费最多 2 倍的空间。
在 Unix 操作系统上,导入 os 包的 Go 程序现在会自动将打开文件限制(RLIMIT_NOFILE)增加到允许的最大值;也就是说,它们将软限制更改为与硬限制匹配。这纠正了某些系统上为与使用 select 系统调用的非常旧的 C 程序兼容而设置的人为低限制。Go 程序不受该限制的帮助,相反,即使是像 gofmt 这样的简单程序在并行处理许多文件时也经常在这些系统上耗尽文件描述符。此更改的一个影响是,反过来在子进程中执行非常旧的 C 程序的 Go 程序可能会以过高的限制运行这些程序。这可以通过在调用 Go 程序之前设置硬限制来纠正。
不可恢复的致命错误(例如并发映射写入或未锁定互斥锁的解锁)现在会打印一个更简单的堆栈跟踪,不包括运行时元数据(等同于致命 panic),除非 GOTRACEBACK=system 或 crash。运行时内部致命错误堆栈跟踪始终包含完整的元数据,无论 GOTRACEBACK 的值如何。
ARM64 上已添加对调试器注入函数调用的支持,使用户在使用更新的调试器时,可以在交互式调试会话中从其二进制文件中调用函数,以利用此功能。
Go 1.18 中添加的地址消毒器支持现在更精确地处理函数参数和全局变量。
编译器
编译器现在使用跳转表来实现大型整数和字符串 switch 语句。switch 语句的性能改进各不相同,但可以达到 20% 的速度提升。(仅限 GOARCH=amd64 和 GOARCH=arm64)
Go 编译器现在需要 -p=importpath 标志来构建可链接的目标文件。这已由 go 命令和 Bazel 提供。任何其他直接调用 Go 编译器的构建系统也需要确保传递此标志。
Go 编译器不再接受 -importmap 标志。直接调用 Go 编译器的构建系统必须改用 -importcfg 标志。
汇编器
与编译器一样,汇编器现在需要 -p=importpath 标志来构建可链接的目标文件。这已由 go 命令提供。任何其他直接调用 Go 汇编器的构建系统也需要确保传递此标志。
链接器
在 ELF 平台上,链接器现在以标准 gABI 格式(SHF_COMPRESSED)发出压缩的 DWARF 节,而不是旧版 .zdebug 格式。
标准库
新的原子类型
sync/atomic 包定义了新的原子类型 Bool、Int32、Int64、Uint32、Uint64、Uintptr 和 Pointer。这些类型隐藏了底层值,以便所有访问都强制使用原子 API。Pointer 也避免了在调用站点转换为 unsafe.Pointer 的需要。Int64 和 Uint64 在结构体和分配的数据中自动对齐到 64 位边界,即使在 32 位系统上也是如此。
PATH 查找
Command 和 LookPath 不再允许从 PATH 搜索中找到相对于当前目录的结果。这消除了常见的安全问题来源,但也可能破坏依赖于使用 exec.Command("prog") 来运行当前目录中名为 prog(或在 Windows 上,prog.exe)的二进制文件的现有程序。有关如何最好地更新此类程序的信息,请参阅 os/exec 包文档。
在 Windows 上,Command 和 LookPath 现在遵守 NoDefaultCurrentDirectoryInExePath 环境变量,使得可以在 Windows 系统上禁用 PATH 查找中默认隐式搜索“.”
对库的微小更改
一如既往,库中进行了各种小的更改和更新,始终牢记 Go 1 的兼容性承诺。还有各种未在此处列出的性能改进。
archive/zip
Reader 现在会忽略 ZIP 文件开头的非 ZIP 数据,与大多数其他实现匹配。这对于读取某些 Java JAR 文件等用途是必需的。
crypto/elliptic
在无效曲线点上操作(即 IsOnCurve 方法返回 false,且从未由 Unmarshal 或对有效点操作的 Curve 方法返回的点)一直都是未定义行为,并可能导致密钥恢复攻击。如果将无效点提供给 Marshal、MarshalCompressed、Add、Double 或 ScalarMult,它们现在将 panic。
在 P224、P384 和 P521 曲线上的 ScalarBaseMult 操作现在快了三倍,导致某些 ECDSA 操作的速度也有类似提升。通用的(非平台优化)P256 实现已替换为来自经过正式验证模型的实现;这可能会导致 32 位平台上的显着减速。
crypto/rand
Read 不再在调用之间缓冲从操作系统获取的随机数据。以高频率执行许多小读取的应用程序可能会选择将 Reader 封装在 bufio.Reader 中以提高性能,同时注意使用 io.ReadFull 以确保不会发生部分读取。
在 Plan 9 上,Read 已重新实现,用快速密钥擦除生成器替换了 ANSI X9.31 算法。
Prime 实现已更改为仅使用拒绝采样,这消除了在非加密上下文中生成小素数时的偏差,消除了一个可能的小计时泄漏,并使行为与 BoringSSL 更好地对齐,同时简化了实现。与以前的实现相比,此更改会为给定的随机源流生成不同的输出,这可能会破坏预期特定确定性随机源产生特定结果的测试。为了帮助防止将来出现此类问题,该实现现在有意地对输入流不确定。
crypto/tls
GODEBUG 选项 tls10default=1 已移除。仍然可以通过设置 Config.MinVersion 来启用 TLS 1.0 客户端。
TLS 服务器和客户端现在拒绝 TLS 握手中的重复扩展,如 RFC 5246 第 7.4.1.4 节和 RFC 8446 第 4.2 节所要求。
crypto/x509
CreateCertificate 不再支持创建 SignatureAlgorithm 设置为 MD5WithRSA 的证书。
CreateCertificate 不再接受负序列号。
当生成的证书没有扩展时,CreateCertificate 不再发出空的 SEQUENCE。
原计划在 Go 1.19 中移除的 GODEBUG 选项 x509sha1=1 已重新安排到未来的版本。使用它的应用程序应着手迁移。自 2017 年以来已证明对 SHA-1 的实际攻击,并且自 2015 年以来,公共信任的证书颁发机构已不再颁发 SHA-1 证书。
ParseCertificate 和 ParseCertificateRequest 现在拒绝包含重复扩展的证书和 CSR。
新的 CertPool.Clone 和 CertPool.Equal 方法分别允许克隆 CertPool 和检查两个 CertPool 的等效性。
新的函数 ParseRevocationList 提供了一个更快、更安全的 CRL 解析器,它返回一个 RevocationList。解析 CRL 还会填充新的 RevocationList 字段 RawIssuer、Signature、AuthorityKeyId 和 Extensions,这些字段被 CreateRevocationList 忽略。
新的方法 RevocationList.CheckSignatureFrom 检查 CRL 上的签名是否是来自 Certificate 的有效签名。
ParseCRL 和 ParseDERCRL 函数现在已弃用,取而代之的是 ParseRevocationList。Certificate.CheckCRLSignature 方法已弃用,取而代之的是 RevocationList.CheckSignatureFrom。
Certificate.Verify 的路径构建器已 overhauled,现在应该可以在复杂场景中生成更好的链和/或更高效。名称约束现在也对非叶证书强制执行。
crypto/x509/pkix
类型 CertificateList 和 TBSCertificateList 已弃用。应改用新的 crypto/x509 CRL 功能。
debug/elf
新的 EM_LOONGARCH 和 R_LARCH_* 常量支持 loong64 端口。
debug/pe
新的 File.COFFSymbolReadSectionDefAux 方法,返回一个 COFFSymbolAuxFormat5,提供了对 PE 文件节中 COMDAT 信息的访问。这些都由新的 IMAGE_COMDAT_* 和 IMAGE_SCN_* 常量支持。
encoding/binary
新的接口 AppendByteOrder 提供了将 uint16、uint32 或 uint64 高效地附加到字节切片的方法。BigEndian 和 LittleEndian 现在实现了此接口。
类似地,新的函数 AppendUvarint 和 AppendVarint 是 PutUvarint 和 PutVarint 的高效附加版本。
encoding/csv
新的方法 Reader.InputOffset 报告读取器的当前输入位置作为字节偏移量,类似于 encoding/json 的 Decoder.InputOffset。
encoding/xml
新的方法 Decoder.InputPos 报告读取器的当前输入位置作为行和列,类似于 encoding/csv 的 Decoder.FieldPos。
flag
新的函数 TextVar 定义了一个带有实现 encoding.TextUnmarshaler 的值的标志,允许命令行标志变量具有 big.Int、netip.Addr 和 time.Time 等类型。
fmt
新的函数 Append、Appendf 和 Appendln 将格式化数据附加到字节切片。
go/parser
解析器现在将 ~x 识别为带运算符 token.TILDE 的一元表达式,允许在不正确的上下文中使用类型约束(例如 ~int)时更好地进行错误恢复。
go/types
新的方法 Func.Origin 和 Var.Origin 返回在类型实例化期间创建的合成 Func 和 Var 对象的通用类型的相应 Object。
通过递归调用 Named.Underlying 或 Named.Method,不再可能生成无限数量的不同但相同的 Named 类型实例化。
hash/maphash
新的函数 Bytes 和 String 提供了一种高效的方法来哈希单个字节切片或字符串。它们等同于使用更通用的 Hash 进行单次写入,但它们避免了小输入的设置开销。
html/template
类型 FuncMap 现在是 text/template 的 FuncMap 的别名,而不是其自己的命名类型。这允许编写在任一设置下操作 FuncMap 的代码。
Go 1.19.8 及更高版本禁止 ECMAScript 6 模板字面量中的操作。此行为可以通过 GODEBUG=jstmpllitinterp=1 设置还原。
image/draw
当目标和源图像都是 image.NRGBA 或都是 image.NRGBA64 时,使用 Src 运算符的 Draw 会保留非预乘 alpha 颜色。这恢复了 Go 1.18 库优化意外引入的行为更改;代码现在与 Go 1.17 及更早版本中的行为匹配。
io
NopCloser 的结果现在实现了 WriterTo,只要其输入实现了。
MultiReader 的结果现在无条件地实现了 WriterTo。如果任何底层读取器未实现 WriterTo,则会进行适当模拟。
mime
仅在 Windows 上,mime 包现在会忽略记录扩展名 .js 应具有 MIME 类型 text/plain 的注册表条目。这是 Windows 系统上常见的无意配置错误。其结果是 .js 将具有默认的 MIME 类型 text/javascript; charset=utf-8。在 Windows 上期望 text/plain 的应用程序现在必须明确调用 AddExtensionType。
mime/multipart
在 Go 1.19.8 及更高版本中,此包设置了其处理的 MIME 数据的大小限制,以防止恶意输入。Reader.NextPart 和 Reader.NextRawPart 将部分中的头数量限制为 10000,Reader.ReadForm 将所有 FileHeaders 中的头总数限制为 10000。这些限制可以通过 GODEBUG=multipartmaxheaders 设置进行调整。Reader.ReadForm 进一步将表单中的部分数量限制为 1000。此限制可以通过 GODEBUG=multipartmaxparts 设置进行调整。
net
纯 Go 解析器现在将使用 EDNS(0) 来包含建议的最大回复数据包长度,允许回复数据包包含多达 1232 字节(之前的最大值为 512)。万一这导致本地 DNS 解析器出现问题,设置环境变量 GODEBUG=netdns=cgo 以使用基于 cgo 的解析器应该可以解决问题。请在问题跟踪器上报告任何此类问题。
当 net 包函数或方法返回“I/O timeout”错误时,该错误现在将满足 errors.Is(err, context.DeadlineExceeded)。当 net 包函数返回“operation was canceled”错误时,该错误现在将满足 errors.Is(err, context.Canceled)。这些更改旨在使代码更容易测试上下文取消或超时导致 net 包函数或方法返回错误的情况,同时保持错误消息的向后兼容性。
Resolver.PreferGo 现在已在 Windows 和 Plan 9 上实现。它以前仅适用于 Unix 平台。结合 Dialer.Resolver 和 Resolver.Dial,现在可以编写可移植程序并在拨号时控制所有 DNS 名称查找。
net 包现在在 Windows 上初步支持 netgo 构建标签。使用时,该包使用 Go DNS 客户端(如 Resolver.PreferGo 所用),而不是向 Windows 请求 DNS 结果。然而,它从 Windows 发现的上游 DNS 服务器在复杂的系统网络配置下可能还不正确。
net/http
ResponseWriter.WriteHeader 现在支持发送用户定义的 1xx 信息性头。
MaxBytesReader 返回的 io.ReadCloser 现在在其读取限制超出时将返回定义的错误类型 MaxBytesError。
HTTP 客户端将处理没有 Location 头部的 3xx 响应,将其返回给调用者,而不是将其视为错误。
net/url
新的 JoinPath 函数和 URL.JoinPath 方法通过连接路径元素列表来创建新的 URL。
URL 类型现在区分没有 authority 的 URL 和带有空 authority 的 URL。例如,http:///path 有一个空 authority(主机),而 http:/path 没有。
当 URL 具有空 authority 时,新的 URL 字段 OmitHost 设置为 true。
os/exec
具有非空 Dir 字段和 nil Env 的 Cmd 现在隐式为子进程设置 PWD 环境变量以匹配 Dir。
新的方法 Cmd.Environ 报告将用于运行命令的环境,包括隐式设置的 PWD 变量。
reflect
方法 Value.Bytes 现在除了切片之外还接受可寻址数组。
方法 Value.Len 和 Value.Cap 现在可以成功地对数组指针进行操作并返回该数组的长度,以匹配 内置 len 和 cap 函数的行为。
regexp/syntax
Go 1.18 发布候选版本 1、Go 1.17.8 和 Go 1.16.15 包含对正则表达式解析器的安全修复,使其拒绝非常深层嵌套的表达式。由于 Go 补丁发布不引入新的 API,在这种情况下解析器返回 syntax.ErrInternalError。Go 1.19 添加了一个更具体的错误 syntax.ErrNestingDepth,解析器现在返回该错误。
runtime
当二进制文件使用 -trimpath 标志构建且进程环境中未设置 GOROOT 变量时,GOROOT 函数现在返回空字符串(而不是 "go")。
runtime/metrics
新的 /sched/gomaxprocs:threads 指标报告当前的 runtime.GOMAXPROCS 值。
新的 /cgo/go-to-c-calls:calls 指标报告从 Go 调用 C 的总次数。此指标与 runtime.NumCgoCall 函数相同。
新的 /gc/limiter/last-enabled:gc-cycle 指标报告上次启用 GC CPU 限制器的 GC 周期。有关 GC CPU 限制器的详细信息,请参阅运行时说明。
runtime/pprof
在收集 goroutine 配置文件时,停止世界暂停时间已显着减少,从而降低了对应用程序的整体延迟影响。
现在,所有 Unix 操作系统都在堆配置文件中报告 MaxRSS(以前仅在 GOOS=android、darwin、ios 和 linux 中报告)。
runtime/race
除了 windows/amd64 和 openbsd/amd64(仍保留在 v2 上)之外,所有支持的平台上的竞争检测器都已升级到使用线程消毒器 v3。与 v2 相比,它现在通常快 1.5 到 2 倍,内存使用量减半,并且支持无限数量的 goroutine。在 Linux 上,竞争检测器现在至少需要 glibc 版本 2.17 和 GNU binutils 2.26。
竞争检测器现在支持 GOARCH=s390x。
对 openbsd/amd64 的竞争检测器支持已从上游线程消毒器中移除,因此不太可能从 v2 更新。
runtime/trace
当同时启用跟踪和 CPU 探查器时,执行跟踪将包含 CPU 探查器样本作为瞬时事件。
sort
排序算法已重写为使用 模式消除快速排序,这在几种常见场景中速度更快。
新的函数 Find 类似于 Search,但通常更容易使用:它返回一个额外的布尔值,报告是否找到了相等的值。
strconv
Quote 和相关函数现在将 Unicode 字符 U+007F 引用为 \x7f,而不是 \u007f,以与其他 ASCII 值保持一致。
syscall
在 PowerPC(GOARCH=ppc64、ppc64le)上,Syscall、Syscall6、RawSyscall 和 RawSyscall6 现在始终为返回值 r2 返回 0,而不是未定义的值。
在 AIX 和 Solaris 上,Getrusage 现在已定义。
time
新的方法 Duration.Abs 提供了一种方便且安全的方式来获取持续时间的绝对值,将 −2⁶³ 转换为 2⁶³−1。(这种边界情况可能由于从零时间减去最近的时间而发生。)
新的方法 Time.ZoneBounds 返回在给定时间生效的时区的开始和结束时间。它可以在循环中使用,以枚举给定位置所有已知的时区转换。