GO1.21 Release Notes

前两天更新Clash,编译时候提示我要更新到1.21,正好详细看看有哪些新东西
原文

介绍

大部分变化体现在工具链toolchain,运行时(runtime),以及库(libraries)的实现。还有版本号的一点小变化,从N.NN.N.N,详情查看Go versions

语言的变化

增加了三个新的内键函数。

  • min, max: 计算给定数的最大最小值。详情
  • clear: 删除一个map的所有对象或是将slice的所有对象零值化。详情

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

  • 根据导入路径排序所有的包
  • 重复以下操作,直到包列表为空:
    • 找到列表中所有导入已经初始化完成的第一个包
    • 初始化该包并从列表中移除

这可能会改变某些程序的行为,因为它们依赖于特定的初始化顺序,而这种顺序并没有通过显式导入来表达。在过去的版本中,规范对此类程序的行为并没有明确定义。新规则提供了明确的定义。

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

  • 现在可以调用一个(可能部分实例化的)泛型函数,其参数本身也是(可能部分实例化的)泛型函数。编译器将尝试推断被调用者缺少的类型参数(如前所述),并为每个未完全实例化的泛型函数参数推断其缺少的类型参数(新参数)。典型的使用场景是调用在容器上运行的泛型函数(例如slices.IndexFunc),在这种情况下,函数参数也可能是泛型的,被调用函数的类型参数及其参数是从容器类型中推断出来的。更常见的情况下,如果可以从赋值中推断出参数的类型,则当将泛型函数分配给变量或作为结果值返回时,就可以使用该函数,而无需显式实例化。
  • 类型推断现在也会在为接口赋值时考虑方法:方法签名中使用的类型参数可以从匹配方法的相应参数类型中推断出来。
  • 同样,由于类型参数必须实现其相应约束条件的所有方法,因此类型参数和约束条件的方法必须匹配,这可能导致推断出更多的类型参数。
  • 如果将多个不同类型的无类型常量参数(例如一个无类型的int和一个无类型的float)传递给具有相同(未指定)类型参数类型的参数,现在类型推断将使用与具有无类型常量操作数的运算符相同的方法来确定类型,而不是出错。这个变化使从无类型常量参数推断出的类型与常量表达式的类型保持一致。
  • 在赋值中匹配相应类型时,类型推断现在变得精确了:组件类型(如slice的元素,或是函数签名中的参数类型)必须相同(给定合适的类型参数)才能匹配,否则推理失败。这一变化使错误信息更加准确:在过去,类型推断可能会错误的成功并导致无效赋值,而现在,如果两个类型不可能匹配,编译器就会报告推断错误。

更广泛的说,语言规范中对类型推论的描述已得到澄清。所有这些变化使得类型推断功能更加强大,推断失败也不再那么令人惊讶。

GO1.21包含我们考虑再GO未来版本中进行的语言更改预览:将for循环变量设置为每次迭代,而不是每个循环,以避免意外共享bug。详细信息,参阅LoopvarExperiment wiki页

GO1.21现在规定,如果一个goroutine panic了,而recover被一个defer函数直接调用,则保证recover的返回值不为nil。为确保这一点,使用接口值为nil(或未键入的nil)调用panic会导致*runtime.PanicNilError类型的运行时panic

为支持为旧版GO编写的程序,可通过设置GODEBUG=panicnil=1重新启用nil panic。如果编译程序的主软件包位与声明GO1.20或更早版本的模块中,则会自动启用此设置。

工具

GO1.21在GO工具链中改进了对向后兼容性和向前兼容性的支持。

为了提高向后兼容性,GO1.21正式规定GO使用GODEBUG环境变量来控制那些根据兼容性策略是非破坏性的,但可能导致现有程序破坏的更改的默认行为。(例如,依赖于错误行为的程序在错误修复后可能会被破坏,但错误修复不被视为破坏性更改。)当GO必须改变这种行为时,它会根据工作区go.work文件中的go行或主模块的go.mod文件在新旧行为之间做出选择。升级到新的GO工具链,但将go行设置为原来的(较旧的)GO版本,可以保留较旧工具链的行为。有了这种兼容性支持,最新的GO工具链应该总是旧版本GO的最佳,最安全的实现。查看Go,向后兼容性和GODBUG了解更多。

为了提高向前兼容性,GO1.21现在将读取go.workgo.mod文件中的go行作为严格的最低要求:go 1.21.0意味着该工作区或模块不能在GO 1.20或 GO 1.21rc1中使用。这样,依赖于GO后期版本修正的项目就可以确保它们不会被早期版本使用。它还能为使用GO新功能的项目提供更好的错误报告:当问题是需要更新的GO版本时,该问题会被清楚的报告,而不是在尝试构建代码时打印出未解决的导入或语法错误。

为了使这些新的更严格的版本要求更易于管理,go命令现在不仅可以调用捆绑在其自身版本中的工具链,还可以调用在PATH中找到的或按需下载的其他GO工具链版本。如果go.modgo.workgo行声明了对GO更新版本的最低要求,go命令将自动查找并运行该版本。新工具链指令设置了建议使用的最小工具链,它可能比严格的go最小工具链更新。详情查看Go Toolchains

Go 命令

现在,-pgo构建标志默认为-pgo=auto,在命令行中指定单个主软件包的限制也已取消。如果主软件包文件夹中存在名为default.pgo的文件,go命令将使用该文件启用配置文件引导的优化,以构建相应的程序。

现在,使用-C dir标志时,必须将其作为命令行的第一个标志。

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

go test -c标志现在支持为多个软件包写入测试二进制文件,每个二进制文件都写入pkg.test,其中pkg是软件包名称。如果正在编译的测试软件包中有一个以上的软件包名称,则会出错。

go test -o现在可以接收文件夹参数,在这种情况下,测试二进制文件将写入该文件夹,而不是当前文件夹。

CGO

import "C"的文件中,如果尝试在C类型上声明GO方法,GO工具链现在会正确报错。

Runtime

在打印非常深的堆栈时,运行时现在会打印前50帧(最内层),然后打印最底层50帧(最外层),而不是只打印前100帧。这使得查看递归堆栈开始的深度变得更容易,对于调试堆栈溢出尤其有价值。

在支持透明大页(transparent huge pages)的Linux平台上,GO运行时现在可以更明确的管理堆的哪些部分可以由大页支持。这样就能更好的利用内存:小型堆应减少内存使用量(在病理情况下最多可减少50%),而大型堆应减少堆中密集部分被破坏的大页,CPU使用率和延迟最多可改善1%。

通过运行时内部垃圾收集调整,应用程序的尾部延迟最多可减少40%,内存使用量也会略有减少。某些应用可能还会观察到吞吐量的少量损失。内存使用量的减少应与吞吐量的损失成正比,因此,只要稍微增加GOGC[和/或]GOMEMLIMIT,就可以恢复上一版本的吞吐量/内存权衡(延迟变化不大)。

在C创建的线程上从C调用GO需要一些设置来准备GO的执行。在UNIX平台,这种设置现在可以在同一线程的多次调用中得到保留。这大大减少了后续C到GO调用的开销,从每次调用约1~3微秒减少到每次调用约100-200纳秒。

Compiler

策略引导优化(PGO),在GO 1.20中作为预览版加入,现在已可普遍使用。PGO可对生产工作负载配置文件确认为常用的代码进行额外优化。如前文GO命令部分所述,对于主软件包文件夹中包含default.pgo配置文件的二进制文件,默认情况下会启用PGO。性能的提升因应用程序行为而异,在一组具有代表性的围棋程序中,大多数程序都能通过启用PGO实现2%到7%的提升。查看PGO user guide了解更多。

PGO构建现在可以将某些接口方法调用虚拟化,为最常见的被调用者添加具体调用。这样就可以进一步优化,例如内联被调用者。

GO1.21将编译速度提高了6%,这主要归功于使用PGO构建编译器本身。

Assembler

这部分和Linker部分,我不太了解,翻译的可能有问题。

amd64架构,无框架nosplit装配函数不再自动标记为NOFRAME。作为替代,如果需要,必须明确指定NOFRAME属性,这在其他支持帧指针的架构上已经是一种行为。这样,运行时就能为堆栈转换维护帧指针。

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

Linker

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

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

核心库

新的log/slog

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

新的testing/slogtest

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

新的slices

新的slices包使用可处理任何元素类型的泛型函数提供了很多切片的常见操作。

新的maps

新的maps包使用可处理任何元素类型的泛型函数提供了很多map的常用操作。

新的cmp

新的cmp定义了类型约束Ordered和两个新的泛型函数LessCompare,它们对于有序类型很有用。

库的小改动

Header.FileInfo返回的io/fs.FileInfo接口的实现现在实现了一个调用io/fs.FormatFileInfoString方法。

FileHeader.FileInfo返回的io/fs.FileInfo接口的实现现在实现了一个调用io/fs.FormatFileInfoString方法。
Reader.Open返回的io/fs.Fileio/fs.ReadDirFile.ReadDir方法返回的io/fs.DirEntry接口的实现现在实现了一个调用io/fs.FormatDirEntryString方法。

Buffer类型有两个新方法:AvailableAvailableBuffer。这些方法可与Write方法一起使用,直接向Buffer追加内容。

新的WithoutCancel函数返回一个上下文副本,该副本在原始上下文被取消时不会被取消。
新的WithDeadlineCauseWithTimeoutCause函数提供了一种在最后期限或计时器到期时设置上下文取消原因的方法。可以使用Cause函数查看原因。
新的AfterFunc函数用于注册一个函数,以便在取消上下文后运行。
优化意味着调用BackgroundTODO并将其转换为共享类型的结果可视为相同。在以前的版本中,它们总是不同的。比较上下文值是否相等从来没有明确的定义,因此这不被认为是不兼容的更改。

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

所有的Curve方法以及GenerateKeyMarshalUnmarshal都已废弃。对于ECDH操作,应使用新的crypto/ecdh包。对于较底层的操作,可使用三方模块,如filippo.io/nistec

该包现在可用在NetBSD 10.0及更高版本上使用getrandom系统调用。

对于GOARCH=amd64GOARCH=arm64,私有RSA操作(解密和签名)的性能比GO 1.19更好。在GO 1.20中,性能有所下降。
由于在PrecomputedValues中添加了私有字段,因此即使反序列化(例如从JSON)先前已预计算的私钥,也必须调用PrivateKey.Precompute以获得最佳性能。
PublicKey.EqualPrivateKey.Equal现在可在恒定时间内执行。
GenerateMultiPrimeKey函数和PrecomputedValues.CRTValues字段已废弃。在调用PrivateKey.Precompute时,PrecomputedValues.CRTValues仍将被填充,但在解密操作中不会使用这些值。

GOARCH=amd64时,SHA-224和SHA-256操作现在使用本地指令,性能提高了3-4倍。

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

应用现在可以控制会话票据的内容。

为了减少会话票据被用作跨连接跟踪机制的可能性,服务器现在会在每次恢复时(如果支持且未禁用)签发新票据,而且票据不再带有加密密钥的标识符。如果向Conn.SetSessionTicketKeys传递大量密钥,可能会导致明显的性能损失。

客户端和服务器现在都实现了扩展主密码扩展(RFC 7627)(Extended Master Secret extension)。已恢复对ConnectionState.TLSUnique的弃用,现在将其设置为支持扩展主密钥的恢复连接。

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

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

改进了服务器发送的客户端身份验证失败的TLS警告代码。之前,这种故障总是导致“坏证书(bad certificate)”警告。现在,根据RFC 5246和RFC 8446的规定,某些故障会导致更恰当的警告代码:

  • 对于TSL 1.3连接,如果服务器配置为使用RequireAnyClientCertRequireAndVerifyClientCert要求客户端验证,而客户端未提供任何证书,服务器现在会返回“需要证书(certificate required)”警告。

  • 如果客户端提供的证书不是由服务器上配置的受信任证书颁发机构签署的,服务器将返回“未知证书颁发机构(unknown certificate authority)”警告

  • 如果客户端提供的证书过期或无效,服务器将返回“证书过期(expired certificate)”警告

  • 在与客户端身份验证失败有关的所有其他情况下,服务器仍会返回“坏证书(bad certificate)”警告

  • crypto/x509

RevocationList.RevokedCertificates已被启用,取而代之的是新的RevokedCertificateEntries字段,它是RevocationListEntry的一个片段。RevocationListEntry包含pkix.RevokedCertificate中的所有字段以及撤销原因代码
名称限制现在可在非叶证书上正确执行,而不是在表达名称限制的证书上执行

新的File.DynValue方法可用于检索指定动态标记列出的数值
DT_FLAGS_1动态标记中允许使用的常量标记现在用DynFlag1类型定义。这些标记的名称以DF_1开头
该包现在定义了常量COMPRESS_ZSTD
该包现在定义了常量R_PPC64_REL24_P9NOTOC

尝试使用Section.DataSection.Open返回的读取器读取包含未初始化数据的部分时,现在会返回错误信息

FS.Open返回的io/fs.File现在有了一个实现io.ReaderAtReadAt方法
调用FS.Open.Stat将返回一个现在实现了String方法的类型,该方法调用[`io/fs.FormatFileInfo](https://go.dev/pkg/io/fs/#FormatFileInfo)

新的NativeEndian变量可用于使用当前机器的本地子节序进行字节片和整数之间的转换

新的ErrUnsupported错误提供了一种标准化的方式,用于表示所请求的操作因不支持而无法执行。例如,在使用不支持硬链接的文件系统时调用os.Link

新的BoolFunc函数和FlagSet.BoolFunc方法定义了一个不需要参数的标志,并在使用标志时调用一个函数。这与Func类似,但针对的是布尔标志
如果已经在同名标志上调用了Set,则标志定义(通过BoolBoolVarIntIntVar等)会出现问题。这一修改旨在检测初始化顺序的变化导致标记操作发生的顺序与预期不同的情况。在许多情况下,解决这个问题的方法是引入一个显式的包依赖关系,以便在进行任何Set操作之前正确的对定义进行排序

新的IsGenerated谓词可报告文件语法树是否包含特殊注释,这种注释通常表示文件是由工具生成的。
新的File.GoVersion字段记录了任何//go:build//+build指令所需的最小GO版本。

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

新的GoVersion函数返回构建表达式所隐含的最小GO版本

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

新的Package.GoVersion方法返回用于检查软件包的GO语言版本

hash/maphash包现在有了纯GO实现,可使用purego构建标记进行选择

当一个操作出现在JavsScript模板文字中时,将返回新错误ErrJSTemplate。此前返回的是未导出错误(unexported error)。

新的FormatFileInfo函数返回FileInfo的格式化版本。新的FormatDirEntry函数返回DirEntry的格式化版本。ReadDir返回的DirEntry实现了一个调用FormatDirEntryString方法,传递给WalkDirFuncDirEntry值也是如此。

新的Int.Float64方法会返回与多精度整数最接近的浮点数值,并显示四舍五入的结果。

在Linux上,当内核支持多路径TCP时,net包现在可以使用多路径TCP。默认情况下不会使用。要在客户端可用时使用多路径TCP,请在调用Dialer.DialDialer.DialContext方法前调用Dialer.SetMultipathTCP方法。要在服务器上可用时使用多路径TCP,请在调用ListenConfig.Listen方法前调用ListenConfig.SetMultipathTCP方法。像往常一样将网络指定为”tcp”, “tcp4”或”tcp6”。如果内核或远程主机不支持多路径TCP,连接将无声的退回到TCP。要测试特定连接是否使用多路径TCP,请使用[`TCPConn.MultipathTCP](https://go.dev/pkg/net/#TCPConn.MultipathTCP)方法。
在未来的GO版本中,我们可能会在支持多路径TCP的系统上默认启用多路径TCP。

新的ResponseController.EnableFullDuplex方法允许服务器处理程序在写入响应的同时读取HTTP/1请求正文。通常情况下,HTTP/1服务器在开始写入响应前,会自动消耗掉所有剩余的请求体,以避免客户端在读取响应前试图写入一个完成的请求而造成死锁。EnableFullDuplex方法会禁止这种行为。
当服务器以HTTP响应回应HTTPS请求时,ClientTransport会返回新的ErrSchemeMismatch错误。
net/http包现在支持errors.ErrUnsupported,表达式errors.Is(http.ErrNotSupported, errors.ErrUnsupported)将返回true

程序现在可以向Chtimes函数传递一个空的time.Time值,以保持访问时间或修改时间不变。
在Windows中,File.Chdir方法现在可将当前文件夹更改为文件,而不是总是返回错误信息。
在Unix系统上,如果向NewFile传递了一个非阻塞描述符,调用File.Fd方法现在将返回一个非阻塞描述符。在此之前,描述符会转换为阻塞模式。
在Windows系统中,在不存在的文件上调用Truncate会创建一个空文件。现在它会返回一个错误,表明文件不存在。
在Windows中调用TempDir时,现在使用GetTempPath2W,而不是GetTempPathW。新行为是一种安全加固措施,可防止以SYSTEM身份运行的进程创建的临时文件被非SYSTEM进程访问。
在Windows系统中,os包现在支持处理无法以有效UTF-8表示而以UTF-16保存的文件名表示的文件。
在Windows中,Lstat现在可为以路径分隔符结尾的路径解析符号链接,这与POSIX平台上的行为一致。
ReadDir函数和File.ReadDir方法返回的io/fs.DirEntry接口的实现现在实现了一个调用io/fs.FormatDirEntryString方法。
DirFS函数返回的io/fs.FS接口的实现现在实现了io/fs.ReadFileFSio/fs.ReadDirFS接口。

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

在GO 1.21中,ValueOf不再强制在堆上分配其参数,而是允许在栈上分配Value的内容。对Value的大多数操作也允许在堆栈中分配底层值。
新的Value方法Value.Clear可清除映射的内容或将片段的内容清零。这与语言中新增的clear内置方法相对应。
SliceHeaderStringHeader类型现已弃用。新代码中,请首选unsafe.Sliceunsafe.SliceDataunsafe.Stringunsafe.StringData

Regexp现在定义了MarshalTextUnmarshalText方法。这些方法实现了encoding.TextMarshalerencoding.TextUnmarshaler,将被encoding/json等包使用。

GO程序产生的文本堆栈跟踪(例如在崩溃,调用runtime.Stack或使用debug=2收集goroutine profile时产生的堆栈跟踪)现在包含了在堆栈跟踪中创建每个goroutinegoroutineID
崩溃的GO应用现在可以通过设置环境变量GOTRACEBACK=wer或在崩溃前调用debug.SetTraceback("wer")来选择加入Windows错误报告(WER)。除启用WER外,运行时的行为与GOTRACEBACK=crash相同。在非Windows系统上,GOTRACEBACK=wer将被忽略。
GODEBUG=cgocheck=2是对cgo指针传递规则的全面检查,不再作为调试选项提供。取而代之的是使用GOEXPERIMENT=cgocheck2作为实验。这意味着必须在构建时而不是启动时选择该模式。
GODEBUG=cgocheck=1仍然可用(且仍然是默认值)。
runtime包中新增了Pinner类型。Pinner可以用来“钉住”GO内存,这样非GO代码就可以更自由的使用它。例如,现在允许向C代码传递引用固定GO内存的GO值。此前,cgo指针传递规则不允许传递任何此类嵌套引用。参阅文档了解更多。

一些以前内部的GC指标,如实时堆大小,现在也可用了。GOGCGOMEMLIMIT现在也可作为指标使用。

现在,在amd64arm64上收集痕迹所需的CPU成本大大降低:与上一版本相比最多可提高10倍。
跟踪现在包含显式停止事件,可用于GO运行时可能停止的各种原因,而不仅仅是垃圾回收。

新的OnceFuncOnceValueOnceValues函数捕捉了Once的一种常用用法,即在首次使用时懒初始化一个值。

在Windows中,Fchdir函数现在可以将当前文件夹更改为其参数,而不是总是返回错误。
在FreeBSD上,SysProcAttr有一个新字段Jail,可用于将新创建的进程置于监禁环境中。
在Windows系统中,syscall包现在支持处理文件名无法以有效UTF-8表示而以UTF-16保存的文件。现在,UTF16ToStringUTF16FromString函数可在UTF-16数据和WTF-8字符串之间进行转换。这是向下兼容的,因为WTF-8是早期版本中使用的UTF-8格式的超集。
有几个错误值与新的errors.ErrUnsupported匹配,因此errors.Is(err, errors.ErrUnsupported)返回true

  • ENOSYS

  • ENOTSUP

  • EOPNOTSUPP

  • EPLAN9 (Plan 9 only)

  • ERROR_CALL_NOT_IMPLEMENTED (Windows only)

  • ERROR_NOT_SUPPORTED (Windows only)

  • EWINDOWS (Windows only)

  • testing

新的-test.fullpath选项将在测试日志信息中打印完整的路径名,而不仅仅是基名。
新的Testing函数会报告程序是否是由go test创建的测试。

调用Open.Stat将返回一个类型,该类型现在实现了调用io/fs.FormatFileInfoString方法。

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

Ports

WebAssembly

现在可以在GO程序中使用新的go:wasmimport指令从WebAssembly主机导入函数。

现在,GO调度器与JS事件循环的交互效率大大提高,尤其是在频繁阻塞异步事件的应用程序中。

WebAssembly System Interface

GO 1.21为WebAssembly系统接口(WASI)预览版1增加了一个实验端口(GOOS=wasip1, GOARCH=wasm)。

由于增加了新的GOOS值”wasip1”,除非使用该GOOS值,否则名为*_wasip1.go的GO文件将被GO工具忽略。如果现有文件名和该模式匹配,需要重新命名。

ppc64/ppc64le

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

在为GOPPC64=power10 GOOS=linux GOARCH=ppc64le构建与位置无关的二进制文件时,用户可以期待在大多数情况下缩小二进制文件的大小,在某些情况下缩小3.5%。与位置无关的二进制文件是为ppc64le构建的,其-buildmode值如下:c-archive, c-shared, shared, pie, plugin

loong64

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


上周准备写的,一下给忘了,拖了一周。