Go 1.22 发布说明

Go 1.22 简介

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

语言更改

Go 1.22 对“for”循环进行了两处更改。

Go 1.22 包含我们正在考虑在未来版本的 Go 中进行的语言更改的预览:遍历函数迭代器。使用 GOEXPERIMENT=rangefunc 构建可以启用此功能。

工具

Go 命令

工作区 中的命令现在可以使用包含工作区依赖项的 vendor 目录。该目录由 go work vendor 创建,并在 -mod 标志设置为 vendor 时由构建命令使用,当工作区 vendor 目录存在时,这是默认设置。

请注意,工作区的 vendor 目录的内容与单个模块的内容不同:如果工作区根目录也包含工作区中的模块之一,则其 vendor 目录可以包含工作区或模块的依赖项,但不能同时包含两者。

在旧版 GOPATH 模式(即,使用 GO111MODULE=off)下,不再支持在模块外部使用 go get。对于旧版 GOPATH 程序,其他构建命令(如 go buildgo test)将无限期地继续工作。

go mod init 不再尝试从其他依赖项管理工具(例如 Gopkg.lock)的配置文件中导入模块需求。

go test -cover 现在会为没有自己测试文件的已覆盖软件包打印覆盖率摘要。在 Go 1.22 之前,对这种软件包执行 go test -cover 会报告

? mymod/mypack [no test files]

现在使用 Go 1.22,软件包中的函数被视为未覆盖

mymod/mypack 覆盖率:语句的 0.0%

请注意,如果软件包根本不包含任何可执行代码,则我们无法报告有意义的覆盖率百分比;对于此类软件包,go 工具将继续报告没有测试文件。

如果将使用外部 (C) 链接器但未启用 cgo,则调用链接器的 go build 命令现在会出错。(Go 运行时需要 cgo 支持以确保它与 C 链接器添加的任何其他库兼容。)

跟踪

作为支持新跟踪器工作的一部分,trace 工具的 Web UI 进行了轻微的更新,解决了几个问题并提高了各个子页面的可读性。Web UI 现在支持以线程为导向的视图浏览跟踪。跟踪查看器现在还显示所有系统调用的完整持续时间。
这些改进仅适用于查看使用 Go 1.22 或更高版本构建的程序生成的跟踪。未来的版本将为旧版 Go 生成的跟踪带来一些这些改进。

Vet

循环变量的引用

vet 工具的行为已更改为匹配 Go 1.22 中循环变量的新语义(请参见上文)。在分析需要 Go 1.22 或更高版本的文件(由于其 go.mod 文件或每个文件的构建约束)时,vet 不再报告从函数字面量中引用循环变量,这些函数字面量可能超出循环的迭代范围。在 Go 1.22 中,每次迭代都会重新创建循环变量,因此此类引用不再有在循环更新变量后使用该变量的风险。

追加后缺少值的新的警告

vet 工具现在会报告对 append 的调用,这些调用没有传递要追加到切片的任何值,例如 slice = append(slice)。此类语句没有任何效果,并且经验表明这几乎总是一个错误。

推迟 time.Since 的新警告

vet 工具现在会报告在 defer 语句中对 time.Since(t) 的非推迟调用。这相当于在 defer 语句之前调用 time.Now().Sub(t),而不是在推迟函数被调用时调用。在几乎所有情况下,正确的代码都需要推迟 time.Since 调用。例如

t := time.Now()
defer log.Println(time.Since(t)) // non-deferred call to time.Since
tmp := time.Since(t); defer log.Println(tmp) // equivalent to the previous defer

defer func() {
  log.Println(time.Since(t)) // a correctly deferred call to time.Since
}()

log/slog 调用中不匹配的键值对的新警告

vet 工具现在会报告对结构化日志记录包 log/slog 中的函数和方法的无效参数调用,这些函数和方法接受交替的键/值对。它会报告在键位置的参数既不是 string 也不是 slog.Attr 的调用,以及缺少其值的最终键的调用。

运行时

运行时现在将基于类型的垃圾回收元数据保留在每个堆对象的附近,从而使 Go 程序的 CPU 性能(延迟或吞吐量)提高 1-3%。此更改还通过消除冗余元数据,将大多数 Go 程序的内存开销减少了大约 1%。某些程序的改进可能较小,因为此更改调整了内存分配器的尺寸类别边界,因此某些对象可能会向上移动一个尺寸类别。

此更改的结果是,某些对象以前始终与 16 字节(或更高)边界对齐的地址现在将仅与 8 字节边界对齐。某些使用需要内存地址大于 8 字节对齐的汇编指令并依赖内存分配器先前对齐行为的程序可能会中断,但我们预计此类程序很少见。可以使用 GOEXPERIMENT=noallocheaders 构建此类程序以恢复旧的元数据布局并恢复先前的对齐行为,但软件包所有者应更新其汇编代码以避免对齐假设,因为此解决方法将在未来的版本中删除。

windows/amd64 端口上,链接或加载使用 -buildmode=c-archive-buildmode=c-shared 构建的 Go 库的程序现在可以使用 SetUnhandledExceptionFilter Win32 函数捕获 Go 运行时未处理的异常。请注意,这在 windows/386 端口上已经受支持。

编译器

配置文件引导优化 (PGO) 构建现在可以比以前取消虚拟化更高的调用比例。来自一组代表性 Go 程序的大多数程序现在在启用 PGO 后,其运行时性能提高了 2% 到 14%。

编译器现在交错取消虚拟化和内联,因此接口方法调用得到更好的优化。

Go 1.22 还包含编译器内联阶段增强实现的预览,该实现使用启发式方法来提高被认为“重要”(例如,在循环中)的调用位置的可内联性,并在被认为“不重要”(例如,在 panic 路径上)的调用位置阻止内联。使用 GOEXPERIMENT=newinliner 构建可以启用新的调用位置启发式方法;有关更多信息和反馈,请参阅 问题 #61502

链接器

链接器的 -s-w 标志现在在所有平台上的行为更加一致。-w 标志抑制 DWARF 调试信息生成。-s 标志抑制符号表生成。-s 标志也暗示 -w 标志,这可以通过 -w=0 来否定。也就是说,-s -w=0 将生成一个带有 DWARF 调试信息生成但没有符号表的二进制文件。

在 ELF 平台上,-B 链接器标志现在接受一种特殊形式:使用 -B gobuildid,链接器将生成一个从 Go 构建 ID 派生的 GNU 构建 ID(ELF NT_GNU_BUILD_ID 注记)。

在 Windows 上,当使用 -linkmode=internal 构建时,链接器现在通过将 .pdata.xdata 部分复制到最终二进制文件中来保留来自 C 对象文件的 SEH 信息。这有助于使用原生工具(如 WinDbg)调试和分析二进制文件。请注意,到目前为止,C 函数的 SEH 异常处理程序未被遵守,因此此更改可能会导致某些程序的行为发生变化。-linkmode=external 不受此更改的影响,因为外部链接器已经保留了 SEH 信息。

引导程序

Go 1.20 版本说明 中所述,Go 1.22 现在需要 Go 1.20 的最终点版本或更高版本进行引导。我们预计 Go 1.24 将需要 Go 1.22 的最终点版本或更高版本进行引导。

标准库

新的 math/rand/v2 包

Go 1.22 包含标准库中的第一个“v2”包,math/rand/v2。与 math/rand 相比的变化在 提案 #61716 中详细说明。最重要的变化是

我们计划在未来的版本(可能是 Go 1.23)中包含一个 API 迁移工具。

新的 go/version 包

新的 go/version 包实现了用于验证和比较 Go 版本字符串的函数。

增强的路由模式

标准库中的 HTTP 路由现在更具表现力。由 net/http.ServeMux 使用的模式已增强为接受方法和通配符。

使用方法注册处理程序,例如 "POST /items/create",将处理程序的调用限制为具有给定方法的请求。具有方法的模式优先于没有方法的匹配模式。作为特例,使用 "GET" 注册的处理程序也会使用 "HEAD" 注册。

模式中的通配符,如 /items/{id},匹配 URL 路径的段。可以通过调用 Request.PathValue 方法来访问实际的段值。以“…”结尾的通配符,如 /files/{path...},必须出现在模式的末尾,并匹配所有剩余的段。

以“/”结尾的模式始终匹配以其为前缀的所有路径。要匹配包括尾部斜杠在内的精确模式,请以 {$} 结尾,如 /exact/match/{$}

如果两个模式在它们匹配的请求中重叠,则更具体的模式优先。如果两者都不更具体,则模式冲突。此规则概括了原始的优先级规则,并保持注册模式的顺序无关紧要的属性。

此更改以细微的方式破坏了向后兼容性,有些是明显的——包含“{”和“}”的模式行为不同——有些则不那么明显——对转义路径的处理已得到改进。更改由名为 httpmuxgo121GODEBUG 字段控制。设置 httpmuxgo121=1 以恢复旧行为。

库的次要更改

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

archive/tar

新方法 Writer.AddFSfs.FS 中的所有文件添加到存档中。

archive/zip

新方法 Writer.AddFSfs.FS 中的所有文件添加到存档中。

bufio

SplitFunc 返回带有 nil 令牌的 ErrFinalToken 时,Scanner 现在将立即停止。以前,它会在停止之前报告最终的空令牌,这通常不是想要的。想要报告最终空令牌的调用者可以通过返回 []byte{} 而不是 nil 来实现。

cmp

新函数 Or 返回序列中第一个非零值。

crypto/tls

ConnectionState.ExportKeyingMaterial 现在将返回错误,除非正在使用 TLS 1.3,或者服务器和客户端都支持 extended_master_secret 扩展。crypto/tls 从 Go 1.20 开始就支持此扩展。这可以通过 tlsunsafeekm=1 GODEBUG 设置禁用。

默认情况下,如果未通过 config.MinimumVersion 指定,则 crypto/tls 服务器提供的最低版本现在为 TLS 1.2,这与 crypto/tls 客户端的行为一致。此更改可以通过 tls10server=1 GODEBUG 设置恢复。

默认情况下,在预 TLS 1.3 握手期间,客户端或服务器不再提供没有 ECDHE 支持的密码套件。此更改可以通过 tlsrsakex=1 GODEBUG 设置恢复。

crypto/x509

新的 CertPool.AddCertWithConstraint 方法可用于向根证书添加自定义约束,以在链构建期间应用。

在 Android 上,根证书现在将从 /data/misc/keychain/certs-added 以及 /system/etc/security/cacerts 加载。

新类型 OID 支持具有大于 31 位的单个组件的 ASN.1 对象标识符。使用此类型的字段 Policies 已添加到 Certificate 结构体中,并在解析过程中填充。任何无法使用 asn1.ObjectIdentifier 表示的 OID 将出现在 Policies 中,但不会出现在旧的 PolicyIdentifiers 字段中。调用 CreateCertificate 时,将忽略 Policies 字段,并从 PolicyIdentifiers 字段获取策略。使用 x509usepolicies=1 GODEBUG 设置可以反转此操作,从 Policies 字段填充证书策略,并忽略 PolicyIdentifiers 字段。我们可能会在 Go 1.23 中更改 x509usepolicies 的默认值,使 Policies 成为编组的默认字段。

database/sql

新的 Null[T] 类型提供了一种方法来扫描任何列类型的可空列。

debug/elf

为 MIPS64 系统定义了常量 R_MIPS_PC32

为 LoongArch 系统定义了其他 R_LARCH_* 常量。

encoding

encoding/base32encoding/base64encoding/hex 包中,每个 Encoding 类型都添加了新的方法 AppendEncodeAppendDecode,通过处理字节切片缓冲区管理来简化从字节切片到字节切片的编码和解码。

base32.Encoding.WithPaddingbase64.Encoding.WithPadding 方法现在如果 padding 参数是 NoPadding 以外的负值,则会引发 panic。

encoding/json

编组和编码功能现在将 '\b''\f' 字符转义为 \b\f,而不是 \u0008\u000c

go/ast

以下与 语法标识符解析 相关的声明现已 弃用Ident.ObjObjectScopeFile.ScopeFile.UnresolvedImporterPackageNewPackage。通常,如果没有类型信息,则无法准确地解析标识符。例如,考虑 T{K: ""} 中的标识符 K:如果 T 是 map 类型,它可能是局部变量的名称;如果 T 是结构体类型,它可能是字段的名称。新程序应该使用 go/types 包来解析标识符;有关详细信息,请参阅 ObjectInfo.UsesInfo.Defs

新的 ast.Unparen 函数从 表达式 中删除任何封闭的 括号

go/types

新的 Alias 类型表示类型别名。之前,类型别名没有显式表示,因此对类型别名的引用等同于拼写出被别名的类型,并且别名名称会丢失。新的表示保留了中间的 Alias。这使得改进错误报告(可以报告类型别名名称)成为可能,并允许更好地处理涉及类型别名的循环类型声明。在未来的版本中,Alias 类型还将携带 类型参数信息。新函数 Unalias 返回由 Alias 类型(或任何其他 Type)表示的实际类型。

因为 Alias 类型可能会破坏不知道检查它们的现有类型开关,所以此功能由名为 gotypesaliasGODEBUG 字段控制。使用 gotypesalias=0 时,所有行为都与之前相同,并且永远不会创建 Alias 类型。使用 gotypesalias=1 时,将创建 Alias 类型,客户端必须预期它们。默认值为 gotypesalias=0。在未来的版本中,默认值将更改为 gotypesalias=1强烈建议 go/types 的客户端尽快调整其代码以使用 gotypesalias=1,以便尽早消除问题。

Info 结构体现在导出 FileVersions 映射,该映射提供每个文件的 Go 版本信息。

新的辅助方法 PkgNameOf 返回给定导入声明的本地包名称。

SizesFor 的实现已调整为在编译器参数为 "gc" 时计算与编译器相同的类型大小。类型检查器现在使用的默认 Sizes 实现为 types.SizesFor("gc", "amd64")

表示函数体的词法环境块 (Scope) 的起始位置 (Pos) 已更改:它过去用于从函数体的大括号开始,但现在从函数的 func 令牌开始。

html/template

JavaScript 模板字面量现在可以包含 Go 模板操作,并且解析包含一个模板字面量的模板将不再返回 ErrJSTemplate。类似地,GODEBUG 设置 jstmpllitinterp 也不再有任何效果。

io

新的 SectionReader.Outer 方法返回传递给 NewSectionReaderReaderAt、偏移量和大小。

log/slog

新的 SetLogLoggerLevel 函数控制 sloglog 包之间桥接的级别。它设置对顶级 slog 日志记录函数的调用的最小级别,并设置通过 slog 调用 log.Logger 的级别。

math/big

新方法 Rat.FloatPrec 计算将有理数准确地表示为浮点数所需的十进制小数位数,以及是否可以在第一时间实现准确的十进制表示。

net

io.CopyTCPConn 复制到 UnixConn 时,它现在将尽可能使用 Linux 的 splice(2) 系统调用,使用新方法 TCPConn.WriteTo

使用“ -tags=netgo”构建时使用的 Go DNS 解析器现在会在执行 DNS 查询之前搜索 Windows hosts 文件(位于 %SystemRoot%\System32\drivers\etc\hosts)中与之匹配的名称。

net/http

新的函数 ServeFileFSFileServerFSNewFileTransportFS 分别是现有 ServeFileFileServerNewFileTransport 的版本,它们作用于 fs.FS

HTTP 服务器和客户端现在拒绝包含无效空 Content-Length 标头的请求和响应。可以通过设置 GODEBUG 字段 httplaxcontentlength=1 来恢复以前的行为。

新方法 Request.PathValue 从请求中返回路径通配符值,新方法 Request.SetPathValue 在请求上设置路径通配符值。

net/http/cgi

在执行 CGI 进程时,PATH_INFO 变量现在始终设置为空字符串或以 / 字符开头的值,如 RFC 3875 所要求。之前,Handler.Root 和请求 URL 的某些组合可能会违反此要求。

net/netip

新的 AddrPort.Compare 方法比较两个 AddrPort

os

在 Windows 上,Stat 函数现在会遵循所有指向系统中另一个命名实体的重新解析点。之前它只遵循 IO_REPARSE_TAG_SYMLINKIO_REPARSE_TAG_MOUNT_POINT 重新解析点。

在 Windows 上,将 O_SYNC 传递给 OpenFile 现在会导致写操作直接写入磁盘,相当于 Unix 平台上的 O_SYNC

在 Windows 上,ReadDirFile.ReadDirFile.ReaddirFile.Readdirnames 函数现在会分批读取目录条目,以减少系统调用的次数,从而将性能提高高达 30%。

io.CopyFile 复制到 net.UnixConn 时,它现在将尽可能使用 Linux 的 sendfile(2) 系统调用,使用新方法 File.WriteTo

os/exec

在 Windows 上,LookPath 现在会忽略 %PATH% 中的空条目,如果找不到可执行文件扩展名来解析一个本来明确的名称,则返回 ErrNotFound(而不是 ErrNotExist)。

在 Windows 上,CommandCmd.Start 如果可执行文件的路径已经是绝对路径并且具有可执行文件扩展名,则不再调用 LookPath。此外,Cmd.Start 不再将解析后的扩展名写回 Path 字段,因此现在可以安全地并发调用 String 方法和 Start 方法。

reflect

Value.IsZero 方法现在将对浮点或复数负零返回 true,如果某个空白字段(名为 _ 的字段)以某种方式具有非零值,则对结构体值返回 true。这些更改使 IsZero 与使用语言 == 运算符将值与零进行比较保持一致。

PtrTo 函数已弃用,建议使用 PointerTo

新函数 TypeFor 返回表示类型参数 T 的 Type。之前,要获取类型的 reflect.Type 值,必须使用 reflect.TypeOf((*T)(nil)).Elem()。现在可以将其写为 reflect.TypeFor[T]()

runtime/metrics

四个新的直方图指标 /sched/pauses/stopping/gc:seconds/sched/pauses/stopping/other:seconds/sched/pauses/total/gc:seconds/sched/pauses/total/other:seconds 提供了有关停止世界暂停的更多详细信息。“stopping”指标报告从决定停止世界到所有 goroutine 停止所花费的时间。“total”指标报告从决定停止世界到再次启动它所花费的时间。

/gc/pauses:seconds 指标已弃用,因为它等同于新的 /sched/pauses/total/gc:seconds 指标。

/sync/mutex/wait/total:seconds 现在除了包含 sync.Mutexsync.RWMutex 之外,还包含对运行时内部锁的争用。

runtime/pprof

互斥锁配置文件现在按阻塞在互斥锁上的 goroutine 数来缩放争用。这提供了对互斥锁在 Go 程序中成为瓶颈的程度的更准确表示。例如,如果 100 个 goroutine 在互斥锁上阻塞了 10 毫秒,则互斥锁配置文件现在将记录 1 秒的延迟,而不是 10 毫秒的延迟。

互斥锁配置文件现在除了包含 sync.Mutexsync.RWMutex 之外,还包含对运行时内部锁的争用。对运行时内部锁的争用始终在 runtime._LostContendedRuntimeLock 处报告。未来的版本将在这些情况下添加完整的堆栈跟踪。

Darwin 平台上的 CPU 配置文件现在包含进程的内存映射,从而可以在 pprof 工具中启用反汇编视图。

runtime/trace

在此版本中,执行跟踪器已完全进行了大修,解决了几个长期存在的问题,并为执行跟踪的新用例铺平了道路。

执行跟踪现在在大多数平台(Windows 除外)上使用操作系统的时钟,因此可以将它们与较低级别组件生成的跟踪相关联。执行跟踪不再依赖于平台时钟的可靠性来生成正确的跟踪。执行跟踪现在会定期动态分区,因此可以以可流式的方式进行处理。执行跟踪现在包含所有系统调用的完整持续时间。执行跟踪现在包含有关 goroutine 执行的操作系统线程的信息。启动和停止执行跟踪的延迟影响已大大降低。执行跟踪现在可以在垃圾回收标记阶段开始或结束。

为了允许 Go 开发人员利用这些改进,可以在 golang.org/x/exp/trace 中使用实验性的跟踪读取包。请注意,此包目前仅适用于使用 Go 1.22 构建的程序生成的跟踪。请尝试使用该包并在 相应的提案问题 上提供反馈。

如果您遇到新执行跟踪器实现的任何问题,可以通过使用 GOEXPERIMENT=noexectracer2 构建 Go 程序来切换回旧实现。如果您这样做,请提交问题,否则此选项将在未来的版本中删除。

slices

新函数 Concat 连接多个切片。

缩小切片大小的函数(DeleteDeleteFuncCompactCompactFuncReplace)现在会将新长度和旧长度之间的元素清零。

如果参数 i 超出范围,Insert 现在始终会引发 panic。之前,如果没有任何元素要插入,则在这种情况下它不会引发 panic。

syscall

自 Go 1.4 以来,syscall 包已 冻结,并在 Go 1.11 中被标记为已弃用,导致许多编辑器警告任何使用该包的情况。但是,某些未弃用的功能需要使用 syscall 包,例如 os/exec.Cmd.SysProcAttr 字段。为了避免对此类代码发出不必要的抱怨,syscall 包不再被标记为已弃用。该包对大多数新功能仍然保持冻结状态,并且鼓励新代码尽可能使用 golang.org/x/sys/unixgolang.org/x/sys/windows

在 Linux 上,新的 SysProcAttr.PidFD 字段允许通过 StartProcessos/exec 启动子进程时获取 PID FD。

在 Windows 上,将 O_SYNC 传递给 Open 现在会导致写操作直接写入磁盘,等同于 Unix 平台上的 O_SYNC

testing/slogtest

新的 Run 函数使用子测试来运行测试用例,提供更细粒度的控制。

端口

Darwin

在 64 位 x86 架构的 macOS 上(darwin/amd64 端口),Go 工具链现在默认生成位置无关可执行文件 (PIE)。可以通过指定 -buildmode=exe 构建标志来生成非 PIE 二进制文件。在基于 64 位 ARM 的 macOS 上(darwin/arm64 端口),Go 工具链已经默认生成 PIE。

Go 1.22 是最后一个可以在 macOS 10.15 Catalina 上运行的版本。Go 1.23 将需要 macOS 11 Big Sur 或更高版本。

Arm

GOARM 环境变量现在允许您选择使用软件浮点还是硬件浮点。以前,有效的 GOARM 值为 567。现在,这些相同的值可以选择后跟 ,softfloat,hardfloat 来选择浮点实现。

此新选项默认为版本 5softfloat 和版本 67hardfloat

Loong64

loong64 端口现在支持使用寄存器传递函数参数和结果。

linux/loong64 端口现在支持地址清理器、内存清理器、新式链接器重定位以及 plugin 构建模式。

OpenBSD

Go 1.22 添加了一个实验性端口到大端 64 位 PowerPC 上的 OpenBSD (openbsd/ppc64)。