Go 1.21 发布说明

Go 1.21 简介

最新的 Go 版本 1.21 在 Go 1.20 发布六个月后发布。其大部分更改都位于工具链、运行时和库的实现中。与往常一样,此版本维护了 Go 1 兼容性承诺;事实上,Go 1.21 改进了该承诺。我们预计几乎所有 Go 程序都将像以前一样继续编译和运行。

Go 1.21 对版本编号做了一个小的更改。过去,我们使用 Go 1.N 来指代 Go 语言版本和发布系列,以及该系列中的第一个版本。从 Go 1.21 开始,第一个版本现在是 Go 1.N.0。今天,我们同时发布了 Go 1.21 语言及其初始实现,即 Go 1.21.0 版本。这些说明指的是“Go 1.21”;像 go version 这样的工具将报告“go1.21.0”(直到您升级到 Go 1.21.1)。有关新版本编号的详细信息,请参阅“Go 工具链”文档中的“Go 版本”。

语言更改

Go 1.21 为语言添加了三个新的内置函数。

现在更精确地指定了包初始化顺序。新的算法是

这可能会改变某些程序的行为,这些程序依赖于过去版本中未由显式导入表达的特定初始化顺序。此类程序的行为在过去的版本中没有得到规范的良好定义。新规则提供了明确的定义。

已经进行了多项改进,以提高类型推断的功能和精度。

更一般地说,语言规范中对 类型推断 的描述已得到澄清。总的来说,所有这些更改使类型推断功能更强大,推断失败也更不容易让人感到意外。

Go 1.21 包含我们正在考虑用于未来 Go 版本的语言更改的预览:使 for 循环变量成为每次迭代的,而不是每个循环的,以避免意外的共享错误。有关如何尝试该语言更改的详细信息,请参阅 LoopvarExperiment wiki 页面

Go 1.21 现在定义,如果一个 goroutine 正在发生恐慌并且 recover 被一个延迟函数直接调用,则保证 recover 的返回值不为 nil。为了确保这一点,使用 nil 接口值(或未类型 nil)调用 panic 会导致类型为 *runtime.PanicNilError 的运行时恐慌。

为了支持为旧版 Go 编写的程序,可以通过设置 GODEBUG=panicnil=1 重新启用 nil 恐慌。当编译主包位于声明 go 1.20 或更早版本的模块中的程序时,会自动启用此设置。

工具

Go 1.21 在 Go 工具链中增加了对向后兼容性和向前兼容性的改进支持。

为了提高向后兼容性,Go 1.21 将 Go 使用 GODEBUG 环境变量来控制根据 兼容性策略 不是破坏性更改但仍可能导致现有程序中断的更改的默认行为。 (例如,依赖于错误行为的程序在修复错误时可能会中断,但错误修复不被视为破坏性更改。)当 Go 必须进行此类行为更改时,它现在会根据工作区 go.work 文件或主模块 go.mod 文件中的 go 行在旧行为和新行为之间进行选择。升级到新的 Go 工具链但将 go 行设置为其原始(旧)Go 版本会保留旧工具链的行为。借助此兼容性支持,最新的 Go 工具链始终应该是旧版 Go 的最佳、最安全的实现。有关详细信息,请参阅“Go、向后兼容性和 GODEBUG”。

为了提高向前兼容性,Go 1.21 现在将 go.workgo.mod 文件中的 go 行读取为严格的最低要求:go 1.21.0 表示工作区或模块不能与 Go 1.20 或 Go 1.21rc1 一起使用。这允许依赖于 Go 后续版本中修复程序的项目确保它们不会与早期版本一起使用。它还为使用新 Go 功能的项目提供了更好的错误报告:当问题是需要更新的 Go 版本时,会清楚地报告该问题,而不是尝试构建代码并打印有关未解析的导入或语法错误的错误。

为了使这些新的更严格的版本要求更容易管理,go 命令现在不仅可以调用捆绑在其自身版本中的工具链,还可以调用在 PATH 中找到或按需下载的其他 Go 工具链版本。如果 go.modgo.work go 行声明了对更新版本 Go 的最低要求,则 go 命令将自动查找并运行该版本。新的 toolchain 指令设置建议使用的最低工具链,这可能比严格的 go 最低值更新。有关详细信息,请参阅“Go 工具链”。

Go 命令

-pgo 构建标志现在默认为 -pgo=auto,并且现在取消了在命令行上指定单个主包的限制。如果主包目录中存在名为 default.pgo 的文件,则 go 命令将使用它来启用配置文件引导优化以构建相应的程序。

当使用时,-C dir 标志现在必须是命令行上的第一个标志。

新的 go test 选项 -fullpath 在测试日志消息中打印完整路径名,而不是仅打印基本名称。

go test -c 标志现在支持为多个包编写测试二进制文件,每个二进制文件都写入 pkg.test,其中 pkg 是包名称。如果要编译的多个测试包具有相同的包名称,则会发生错误。

go test -o 标志现在接受目录参数,在这种情况下,测试二进制文件将写入该目录而不是当前目录。

当启用 cgo 与外部(C)链接器一起使用时,runtime/cgo 包现在作为附加依赖项提供给 Go 链接器,以确保 Go 运行时与 C 链接器添加的任何其他库兼容。

Cgo

在导入 "C" 的文件中,Go 工具链现在可以正确报告尝试在 C 类型上声明 Go 方法的错误。

运行时

在打印非常深的堆栈时,运行时现在打印前 50 个(最内部)帧,然后打印后 50 个(最外部)帧,而不是只打印前 100 个帧。这使得更容易查看递归堆栈的深度起点,对于调试堆栈溢出尤其有用。

在支持透明大页面的 Linux 平台上,Go 运行时现在更明确地管理堆的哪些部分可能由大页面支持。这导致更好的内存利用率:小型堆应该会看到更少的内存使用(在病态情况下最多 50%),而大型堆应该会看到堆密集部分的损坏大页面更少,从而将 CPU 使用率和延迟提高最多 1%。此更改的后果是运行时不再尝试解决特定的有问题的 Linux 配置设置,这可能会导致更高的内存开销。建议的解决方法是根据 GC 指南 调整操作系统的巨型页面设置。但是,还有其他解决方法可用。请参阅 关于 max_ptes_none 的部分

由于运行时内部垃圾回收调整,应用程序可能会看到应用程序尾部延迟降低高达 40%,并且内存使用量略有下降。某些应用程序也可能会观察到吞吐量略有下降。内存使用量的减少应该与吞吐量的减少成正比,因此可以通过稍微增加 GOGC 和/或 GOMEMLIMIT 来恢复先前版本的吞吐量/内存权衡(对延迟几乎没有影响)。

从 C 线程到 Go 的调用需要一些设置才能准备 Go 执行。在 Unix 平台上,此设置现在在同一线程的多个调用中得以保留。这显着降低了后续 C 到 Go 调用的开销,从每次调用约 1-3 微秒降低到每次调用约 100-200 纳秒。

编译器

配置文件引导优化 (PGO) 在 Go 1.20 中作为预览版添加,现在已准备好投入通用使用。PGO 可以对配置文件中标识为热点的生产工作负载代码进行额外的优化。如 Go 命令部分 所述,对于主包目录中包含 default.pgo 配置文件的二进制文件,默认情况下启用 PGO。性能改进因应用程序行为而异,在一组代表性 Go 程序的大多数程序中,启用 PGO 后性能提高了 2% 到 7%。有关详细文档,请参阅 PGO 用户指南

PGO 构建现在可以取消虚拟化一些接口方法调用,并将具体调用添加到最常见的被调用者。这使得可以进行进一步的优化,例如内联被调用者。

Go 1.21 将构建速度提高了最多 6%,这主要归功于使用 PGO 构建编译器本身。

汇编器

在 amd64 上,无帧 nosplit 汇编函数不再自动标记为 NOFRAME。相反,如果需要,必须显式指定 NOFRAME 属性,这已经是支持帧指针的其他架构上的行为。通过此,运行时现在维护用于堆栈转换的帧指针。

已改进在 amd64 上动态链接时检查 R15 错误使用的验证器。

链接器

在 windows/amd64 上,链接器(在编译器的帮助下)现在默认发出 SEH 展开数据,这改善了 Go 应用程序与 Windows 调试器和其他工具的集成。

在 Go 1.21 中,链接器(在编译器的帮助下)现在能够删除未引用的全局映射变量,如果变量初始化程序中的条目数量足够大,并且如果初始化程序表达式没有副作用。

标准库

新的 log/slog 包

新的 log/slog 包提供了具有级别的结构化日志记录。结构化日志记录发出键值对,以便快速、准确地处理大量日志数据。该包支持与流行的日志分析工具和服务集成。

新的 testing/slogtest 包

新的 testing/slogtest 包可以帮助验证 slog.Handler 实现。

新的 slices 包

新的 slices 包提供了许多对切片的常见操作,使用适用于任何元素类型的切片的泛型函数。

新的 maps 包

新的 maps 包提供了对映射的几个常见操作,使用适用于任何键或元素类型的映射的泛型函数。

新的 cmp 包

新的 cmp 包定义了类型约束 Ordered 以及两个新的泛型函数 LessCompare,这些函数与 有序类型 一起使用非常有用。

库的次要更改

与往常一样,对库进行了各种细微的更改和更新,并牢记 Go 1 的 兼容性承诺。还有一些各种性能改进,此处未列出。

archive/tar

Header.FileInfo 返回的 io/fs.FileInfo 接口的实现现在实现了 String 方法,该方法调用 io/fs.FormatFileInfo

archive/zip

FileHeader.FileInfo 返回的 io/fs.FileInfo 接口的实现现在实现了 String 方法,该方法调用 io/fs.FormatFileInfo

Reader.Open 返回的 io/fs.Fileio/fs.ReadDirFile.ReadDir 方法返回的 io/fs.DirEntry 接口的实现现在实现了 String 方法,该方法调用 io/fs.FormatDirEntry

bytes

Buffer 类型有两个新方法:AvailableAvailableBuffer。这些可以与 Write 方法一起使用,直接附加到 Buffer

context

新的 WithoutCancel 函数返回上下文的一个副本,当原始上下文被取消时,该副本不会被取消。

新的 WithDeadlineCauseWithTimeoutCause 函数提供了一种方法,在截止日期或计时器到期时设置上下文取消原因。可以使用 Cause 函数检索原因。

新的 AfterFunc 函数注册一个函数,以便在上下文被取消后运行。

一项优化意味着调用 BackgroundTODO 并将其转换为共享类型的结果可以被认为是相等的。在以前的版本中,它们始终不同。比较 Context 值是否相等从未被很好地定义,因此这不被认为是不兼容的更改。

crypto/ecdsa

PublicKey.EqualPrivateKey.Equal 现在以恒定时间执行。

crypto/elliptic

所有 Curve 方法都已弃用,以及 GenerateKeyMarshalUnmarshal。对于 ECDH 操作,应改用新的 crypto/ecdh 包。对于较低级别的操作,请使用第三方模块,例如 filippo.io/nistec

crypto/rand

crypto/rand 包现在在 NetBSD 10.0 及更高版本上使用 getrandom 系统调用。

crypto/rsa

对于 GOARCH=amd64GOARCH=arm64,私有 RSA 操作(解密和签名)的性能现在优于 Go 1.19。它在 Go 1.20 中有所下降。

由于在 PrecomputedValues 中添加了私有字段,因此即使反序列化(例如从 JSON)先前预计算的私钥,也必须调用 PrivateKey.Precompute 以获得最佳性能。

PublicKey.EqualPrivateKey.Equal 现在以恒定时间执行。

GenerateMultiPrimeKey 函数和 PrecomputedValues.CRTValues 字段已弃用。PrecomputedValues.CRTValues 在调用 PrivateKey.Precompute 时仍将填充,但在解密操作期间不会使用这些值。

crypto/sha256

GOARCH=amd64 时,SHA-224 和 SHA-256 操作现在使用本机指令(如果可用),从而提供大约 3-4 倍的性能提升。

crypto/tls

除了检查过期时间外,服务器现在会跳过验证恢复连接的客户端证书(包括不运行 Config.VerifyPeerCertificate)。当使用客户端证书时,这会使会话票证更大。客户端在恢复时已经跳过验证,但现在即使设置了 Config.InsecureSkipVerify 也会检查过期时间。

应用程序现在可以控制会话票证的内容。

为了减少会话票证被用作跨连接跟踪机制的可能性,服务器现在在每次恢复时(如果支持并且未禁用)都会发出新票证,并且票证不再带有加密它们的密钥的标识符。如果将大量密钥传递给 Conn.SetSessionTicketKeys,这可能会导致明显的性能成本。

客户端和服务器现在都实现了扩展主密钥扩展 (RFC 7627)。ConnectionState.TLSUnique 的弃用已被撤消,现在已针对支持扩展主密钥的恢复连接设置。

新的 QUICConn 类型提供了对 QUIC 实现的支持,包括 0-RTT 支持。请注意,这本身不是 QUIC 实现,并且 0-RTT 仍不受 TLS 支持。

新的 VersionName 函数返回 TLS 版本号的名称。

已改进从服务器发送的客户端身份验证失败的 TLS 警报代码。以前,这些失败始终导致“错误证书”警报。现在,某些失败将导致更合适的警报代码,如 RFC 5246 和 RFC 8446 中所定义。

crypto/x509

RevocationList.RevokedCertificates 已被弃用,并替换为新的 RevokedCertificateEntries 字段,该字段是 RevocationListEntry 的切片。 RevocationListEntry 包含 pkix.RevokedCertificate 中的所有字段,以及吊销原因代码。

名称约束现在在非叶子证书上正确执行,而不是在表达它们的证书上执行。

debug/elf

新的 File.DynValue 方法可用于检索与给定动态标记一起列出的数值。

DT_FLAGS_1 动态标记中允许的常量标志现在使用类型 DynFlag1 定义。这些标记的名称以 DF_1 开头。

该包现在定义了常量 COMPRESS_ZSTD

该包现在定义了常量 R_PPC64_REL24_P9NOTOC

debug/pe

使用 Section.DataSection.Open 返回的读取器从包含未初始化数据的节中读取的尝试现在将返回错误。

embed

FS.Open 返回的 io/fs.File 现在具有一个 ReadAt 方法,该方法实现了 io.ReaderAt

调用 FS.Open.Stat 将返回一个现在实现 String 方法的类型,该方法调用 io/fs.FormatFileInfo

encoding/binary

新的 NativeEndian 变量可用于使用当前机器的原生字节序在字节切片和整数之间进行转换。

errors

新的 ErrUnsupported 错误提供了一种标准化的方法来指示无法执行请求的操作,因为它不受支持。例如,当使用不支持硬链接的文件系统时,调用 os.Link

flag

新的 BoolFunc 函数和 FlagSet.BoolFunc 方法定义了一个不需要参数的标志,并在使用该标志时调用一个函数。这类似于 Func,但适用于布尔标志。

如果已对同名标志调用过 Set,则标志定义(通过 BoolBoolVarIntIntVar 等)将导致 panic。此更改旨在检测 初始化顺序更改 导致标志操作以不同于预期的顺序发生的情况。在许多情况下,解决此问题的办法是引入显式包依赖关系,以在任何 Set 操作之前正确排序定义。

go/ast

新的 IsGenerated 谓词报告文件语法树是否包含 特殊注释,该注释通常表示该文件是由工具生成的。

新的 File.GoVersion 字段记录任何 //go:build// +build 指令所需的最低 Go 版本。

go/build

该包现在解析文件头(在 package 声明之前)中的构建指令(以 //go: 开头的注释)。这些指令在新的 Package 字段 DirectivesTestDirectivesXTestDirectives 中可用。

go/build/constraint

新的 GoVersion 函数返回构建表达式隐含的最低 Go 版本。

go/token

新的 File.Lines 方法以与 File.SetLines 接受的相同形式返回文件的行号表。

go/types

新的 Package.GoVersion 方法返回用于检查包的 Go 语言版本。

hash/maphash

hash/maphash 包现在具有一个纯 Go 实现,可以使用 purego 构建标签进行选择。

html/template

当操作出现在 JavaScript 模板文字中时,将返回新的错误 ErrJSTemplate。以前返回的是一个未导出的错误。

io/fs

新的 FormatFileInfo 函数返回 FileInfo 的格式化版本。新的 FormatDirEntry 函数返回 DirEntry 的格式化版本。 ReadDir 返回的 DirEntry 的实现现在实现了一个 String 方法,该方法调用 FormatDirEntry,并且 WalkDirFunc 传递的 DirEntry 值也是如此。

math/big

新的 Int.Float64 方法返回最接近多精度整数的浮点值,以及任何发生的舍入指示。

net

在 Linux 上,当内核支持时,net 包现在可以使用多路径 TCP。默认情况下不使用它。要在客户端使用可用时的多路径 TCP,请在调用 Dialer.DialDialer.DialContext 方法之前,调用 Dialer.SetMultipathTCP 方法。要在服务器上使用可用时的多路径 TCP,请在调用 ListenConfig.Listen 方法之前,调用 ListenConfig.SetMultipathTCP 方法。像往常一样将网络指定为 "tcp""tcp4""tcp6"。如果内核或远程主机不支持多路径 TCP,则连接将静默回退到 TCP。要测试特定连接是否正在使用多路径 TCP,请使用 TCPConn.MultipathTCP 方法。

在未来的 Go 版本中,我们可能会在支持它的系统上默认启用多路径 TCP。

net/http

新的 ResponseController.EnableFullDuplex 方法允许服务器处理程序在写入响应的同时并发地从 HTTP/1 请求正文中读取。通常,HTTP/1 服务器在开始写入响应之前会自动使用任何剩余的请求正文,以避免客户端死锁,这些客户端尝试在读取响应之前写入完整的请求。EnableFullDuplex 方法禁用此行为。

当服务器对 HTTPS 请求以 HTTP 响应进行响应时,ClientTransport 将返回新的错误 ErrSchemeMismatch

net/http 包现在支持 errors.ErrUnsupported,因为表达式 errors.Is(http.ErrNotSupported, errors.ErrUnsupported) 将返回 true。

os

程序现在可以将空 time.Time 值传递给 Chtimes 函数,以使访问时间或修改时间保持不变。

在 Windows 上,File.Chdir 方法现在将当前目录更改为文件,而不是始终返回错误。

在 Unix 系统上,如果将非阻塞描述符传递给 NewFile,则调用 File.Fd 方法现在将返回非阻塞描述符。以前,描述符被转换为阻塞模式。

在 Windows 上,对不存在的文件调用 Truncate 用于创建空文件。它现在返回一个指示文件不存在的错误。

在 Windows 上,调用 TempDir 现在在可用时使用 GetTempPath2W,而不是 GetTempPathW。新行为是一种安全强化措施,可防止以 SYSTEM 身份运行的进程创建的临时文件被非 SYSTEM 进程访问。

在 Windows 上,os 包现在支持使用文件,其名称(存储为 UTF-16)无法表示为有效的 UTF-8。

在 Windows 上,Lstat 现在解析以路径分隔符结尾的路径的符号链接,与其在 POSIX 平台上的行为一致。

ReadDir 函数和 File.ReadDir 方法返回的 io/fs.DirEntry 接口的实现现在实现了一个 String 方法,该方法调用 io/fs.FormatDirEntry

DirFS 函数返回的 io/fs.FS 接口的实现现在实现了 io/fs.ReadFileFSio/fs.ReadDirFS 接口。

path/filepath

传递给 WalkDir 的函数参数的 io/fs.DirEntry 接口的实现现在实现了一个 String 方法,该方法调用 io/fs.FormatDirEntry

reflect

在 Go 1.21 中,ValueOf 不再强制其参数在堆上分配,允许 Value 的内容在栈上分配。Value 上的大多数操作也允许底层值在栈上分配。

新的 Value 方法 Value.Clear 清除映射的内容或将切片的内容清零。这对应于 添加到语言中的 新的 clear 内置函数。

SliceHeaderStringHeader 类型现在已弃用。在新代码中,更喜欢使用 unsafe.Sliceunsafe.SliceDataunsafe.Stringunsafe.StringData

regexp

Regexp 现在定义了 MarshalTextUnmarshalText 方法。这些实现了 encoding.TextMarshalerencoding.TextUnmarshaler,并将被诸如 encoding/json 之类的包使用。

runtime

Go 程序生成的文本堆栈跟踪(例如,在崩溃时、调用 runtime.Stack 时或使用 debug=2 收集 goroutine 配置文件时生成的堆栈跟踪)现在包含创建堆栈跟踪中每个 goroutine 的 goroutine 的 ID。

崩溃的 Go 应用程序现在可以通过设置环境变量GOTRACEBACK=wer或在崩溃前调用debug.SetTraceback("wer")来选择加入 Windows 错误报告 (WER)。除了启用 WER 外,运行时的行为与GOTRACEBACK=crash相同。在非 Windows 系统上,GOTRACEBACK=wer会被忽略。

GODEBUG=cgocheck=2(一个彻底检查 cgo 指针传递规则的检查器)不再作为调试选项可用。取而代之的是,它作为一项实验使用GOEXPERIMENT=cgocheck2提供。特别是,这意味着此模式必须在构建时而不是启动时选择。

GODEBUG=cgocheck=1仍然可用(并且仍然是默认值)。

运行时包中添加了一种新的类型PinnerPinner可用于“固定”Go内存,以便非 Go 代码可以更自由地使用它。例如,现在允许将引用固定 Go 内存的 Go 值传递给 C 代码。以前,cgo 指针传递规则不允许传递任何此类嵌套引用。有关更多详细信息,请参阅文档

runtime/metrics

现在可以访问一些以前内部的 GC 指标,例如活动堆大小。GOGCGOMEMLIMIT现在也可用作指标。

runtime/trace

在 amd64 和 arm64 上收集跟踪现在产生的 CPU 成本大大降低:与上一版本相比,最多提高了 10 倍。

跟踪现在包含每个 Go 运行时可能停止世界的事件的显式停止世界事件,而不仅仅是垃圾回收。

sync

新的OnceFuncOnceValueOnceValues函数捕获了Once的一个常见用法,即在第一次使用时延迟初始化一个值。

syscall

在 Windows 上,Fchdir函数现在将当前目录更改为其参数,而不是始终返回错误。

在 FreeBSD 上,SysProcAttr有一个新的字段Jail,可用于将新创建的进程置于一个监禁的环境中。

在 Windows 上,syscall 包现在支持处理名称(以 UTF-16 存储)无法表示为有效 UTF-8 的文件。UTF16ToStringUTF16FromString函数现在在 UTF-16 数据和WTF-8字符串之间进行转换。这是向后兼容的,因为 WTF-8 是早期版本中使用的 UTF-8 格式的超集。

几个错误值与新的errors.ErrUnsupported匹配,因此errors.Is(err, errors.ErrUnsupported)返回 true。

testing

新的-test.fullpath选项将在测试日志消息中打印完整路径名,而不仅仅是基本名称。

新的Testing函数报告程序是否是由go test创建的测试。

testing/fstest

调用Open.Stat将返回一个现在实现String方法的类型,该方法调用io/fs.FormatFileInfo

unicode

系统中的unicode包和相关支持已升级到Unicode 15.0.0

端口

Darwin

如 Go 1.20 发行说明中宣布的那样,Go 1.21 需要 macOS 10.15 Catalina 或更高版本;对以前版本的支持已停止。

Windows

如 Go 1.20 发行说明中宣布的那样,Go 1.21 需要至少 Windows 10 或 Windows Server 2016;对以前版本的支持已停止。

WebAssembly

新的go:wasmimport指令现在可以在 Go 程序中用于从 WebAssembly 宿主导入函数。

Go 调度程序现在与 JavaScript 事件循环的交互效率更高,尤其是在经常阻塞异步事件的应用程序中。

WebAssembly 系统接口

Go 1.21 添加了一个实验性端口到WebAssembly 系统接口 (WASI),预览版 1(GOOS=wasip1GOARCH=wasm)。

由于添加了新的GOOS值“wasip1”,因此现在将忽略名为*_wasip1.go的 Go 文件被 Go 工具忽略,除非正在使用该GOOS值。如果您有与该模式匹配的现有文件名,则需要重命名它们。

ppc64/ppc64le

在 Linux 上,GOPPC64=power10现在生成 PC 相对指令、前缀指令和其他新的 Power10 指令。在 AIX 上,GOPPC64=power10生成 Power10 指令,但不生成 PC 相对指令。

当为GOPPC64=power10 GOOS=linux GOARCH=ppc64le构建位置无关二进制文件时,用户在大多数情况下可以预期二进制文件大小会减小,在某些情况下可减少 3.5%。使用以下-buildmode值构建 ppc64le 的位置无关二进制文件:c-archivec-sharedsharedpieplugin

loong64

linux/loong64端口现在支持-buildmode=c-archive-buildmode=c-shared-buildmode=pie