Go 1.22 发布说明

Go 1.22 简介

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

语言变化

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

  • 以前,“for”循环声明的变量只创建一次,并由每次迭代更新。在 Go 1.22 中,循环的每次迭代都会创建新变量,以避免意外的共享错误。提案中描述的过渡支持工具与 Go 1.21 中的工作方式相同。

  • “For”循环现在可以迭代整数。例如:示例

    package main
    
    import "fmt"
    
    func main() {
      for i := range 10 {
        fmt.Println(10 - i)
      }
      fmt.Println("go1.22 has lift-off!")
    }
    

    有关详细信息,请参阅规范。

Go 1.22 包含我们正在考虑用于未来 Go 版本的语言更改的预览:基于函数的迭代器。使用 GOEXPERIMENT=rangefunc 构建将启用此功能。

工具

Go 命令

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

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

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

go mod init 不再尝试从其他 vendoring 工具(例如 Gopkg.lock)的配置文件中导入模块要求。

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

? mymod/mypack [no test files]

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

mymod/mypack coverage: 0.0% of statements

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

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

Trace

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

Vet

对循环变量的引用

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

append 后缺少值的新警告

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

defer 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 中有详细说明。最重要的更改是

  • math/rand 中已弃用的 Read 方法并未在 math/rand/v2 中沿用。(它在 math/rand 中仍然可用。)绝大多数 Read 调用应改用 crypto/randRead。否则,可以使用 Uint64 方法构造自定义的 Read
  • 顶级函数访问的全局生成器是无条件随机种子化的。由于 API 不保证固定的结果序列,因此现在可以实现诸如每线程随机生成器状态之类的优化。
  • Source 接口现在有一个单一的 Uint64 方法;没有 Source64 接口。
  • 许多方法现在使用更快的算法,这些算法在 math/rand 中无法采用,因为它们改变了输出流。
  • math/rand 中的 IntnInt31Int31nInt63Int64n 顶级函数和方法在 math/rand/v2 中拼写更符合习惯用法:IntNInt32Int32NInt64Int64N。还有新的顶级函数和方法 Uint32Uint32NUint64Uint64NUintN
  • 新的泛型函数 N 类似于 Int64NUint64N,但适用于任何整数类型。例如,从 0 到 5 分钟的随机持续时间是 rand.N(5*time.Minute)
  • math/randSource 提供的 Mitchell & Reeds LFSR 生成器已被两个更现代的伪随机生成器源取代:ChaCha8PCG。ChaCha8 是一种新的、密码学上强大的随机数生成器,效率与 PCG 大致相似。ChaCha8 是 math/rand/v2 中顶级函数使用的算法。从 Go 1.22 开始,math/rand 的顶级函数(未明确种子化时)和 Go 运行时也使用 ChaCha8 进行随机性。

我们计划在未来的版本中(很可能是 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 支持 ASN.1 对象标识符,其单个组件大于 31 位。Certificate 结构中添加了一个使用此类型的新字段 Policies,并且现在在解析期间填充。任何无法使用 asn1.ObjectIdentifier 表示的 OID 将出现在 Policies 中,但不会出现在旧的 PolicyIdentifiers 字段中。调用 CreateCertificate 时,Policies 字段将被忽略,策略将从 PolicyIdentifiers 字段中获取。使用 x509usepolicies=1 GODEBUG 设置会反转此行为,从 Policies 字段填充证书策略,并忽略 PolicyIdentifiers 字段。我们可能会在 Go 1.23 中更改 x509usepolicies 的默认值,使 Policies 成为封送的默认字段。

database/sql

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

debug/elf

定义了常量 R_MIPS_PC32 以用于 MIPS64 系统。

定义了额外的 R_LARCH_* 常量以用于 LoongArch 系统。

编码

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

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

encoding/json

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

go/ast

以下与句法标识符解析相关的声明现在已弃用Ident.ObjObjectScopeFile.ScopeFile.UnresolvedImporterPackageNewPackage。通常,在没有类型信息的情况下无法准确解析标识符。例如,考虑 T{K: ""} 中的标识符 K:如果 T 是映射类型,它可能是局部变量的名称;如果 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" 时,SizesFor 的实现已调整为计算与编译器相同的类型大小。类型检查器使用的默认 Sizes 实现现在是 types.SizesFor("gc", "amd64")

表示函数体的词法环境块(Scope)的起始位置(Pos)已更改:以前它从函数体的左花括号开始,但现在从函数的 func 标记开始。

html/template

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

io

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

日志/slog

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

math/big

新方法 Rat.FloatPrec 计算将有理数精确表示为浮点数所需的十进制小数位数,以及是否首先可能进行精确的十进制表示。

net

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

Go DNS 解析器(在使用“-tags=netgo”构建时使用)现在会在进行 DNS 查询之前,在 Windows 主机文件(位于 %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.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 字段,因此现在可以安全地在调用 Start 的同时并发调用 String 方法。

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

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

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

为了让 Go 开发者能够利用这些改进,可以在 golang.org/x/exp/trace 获取一个实验性跟踪读取包。请注意,目前此包仅适用于由 Go 1.22 或更高版本构建的程序生成的跟踪。请尝试使用该包并在相应的提案问题上提供反馈。

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

切片

新函数 Concat 连接多个切片。

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

如果参数 i 超出范围,Insert 现在总是 panic。以前,在这种情况下如果没有要插入的元素,它不会 panic。

syscall

syscall 包自 Go 1.4 以来一直处于冻结状态,并在 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)。