Go 1.18 发布说明

拟定发布 - Go 1.18介绍

Go 1.18还没有发布。这些是正在进行中的发布说明。Go 1.18预计将于2022年2月发布。

Go语言的变化

泛型

Go 1.18包含一个在类型参数提案中描述的泛型特性的实现,这包括对语言的重大—但完全向后兼容的—改变。

这些新的变化需要编写大量新的代码,然而这些代码还没有在生产环境中进行过大量的测试。这只会在当越来越多人编写和使用泛型的时候才会发生。我们相信这些新特性的实现是优雅的,并且是高质量的。然而,与Go大多数方面不同的是,我们无法用现实世界的经验来支持这种信念。因此,虽然我们鼓励在某些需要使用的泛型的场景下使用泛型,但当部署泛型代码到生产环境时,请多谨慎一些。

以下是最明显的变化列表。关于更全面的概述,请看提案。详情见语言规范

  • 函数类型宣告的语法,现在接受类型参数
  • 参数化的函数和类型可以通过在它们后面加上类型参数列表来进行实例化,类型参数列表放在方括号中。
  • 添加了新的标识 ~运算符和标点符号
  • 接口类型的语法现在已经支持嵌套任意类型(不只是接口的类型名称),以及union和 ~T 类型元素。这样的接口只能作为类型约束使用。现在一个接口定义了一组类型以及一组方法。
  • 新的提前宣告标识符(prdeclared identifyer) any 作为空接口的别名。这将取代原有的 interface{}
  • 新的提前宣告标识符(prdeclared identifyer) comparable 是一个接口表示所有可以通过==或者!=来比较的类型的集合。它只能用于(或者嵌套)一个类型约束。

有三个使用泛型实现的实验性质的包可能会有用。这些包在 x/exp仓库;它们的API不在Go的保证范围内,而且随着我们对泛型经验的增加,可能会发生变化。

现在泛型的实现还有如下的限制:

  • Go编译器目前不能处理泛型函数或者方法内的类型声明。我们希望在Go 1.19提供这个特性。
  • Go编译器目前在预先声明函数的参数类型上不支持realimagcomplex类型。我们希望在Go 1.19移除这个限制。
  • 如果m是由P的约束接口明确声明的,那Go编译器目前只支持在参数类型为P的值x上调用方法m。同样的,只在m是由P的约束接口明确声明的,才支持方法值x.m和方法表达式P.m, 即使由于P中的所有类型都实现了m,导致mP的方法集中也不能支持上述的使用方式。我们希望在Go 1.19移除这个限制。
  • 不允许将类型参数或指向类型参数的指针作为结构类型中的未命名字段嵌入。同样的,不允许将类型参数嵌入到一个接口类型。这个是否会被允许,目前还是未知的。
  • 有一个以上元素的集合元素不能包含一个具有非空方法集的接口类型。这个是否会被允许,目前还是未知的。

Bug修复

Go 1.18 编译器现在可以正确报告在函数字面中设置,但从未使用的变量的声明错误。在Go 1.18之前,编译器在这种情况下不会报告错误。这修复了长期存在的编译器问题。由于这一变化,(可能是不正确的)程序可能不再被编译。可以直接这么修改:如果程序实际上是不正确的,就修复它,或者使用了违规的变量,例如把它赋值给空白标识符_。由于go vet总是指出这个错误,受影响的程序数量可能非常少。

当将一个符文常量表达式,例如'1' << 32,作为一个参数传递给预先声明函数printpintln 时,Go 1.18编译器会报告溢出,与用户定义的函数的行为一致。在Go 1.18之前,编译器对此情况不是报错误,而是如果它符合int64的要求,就会接收它。由于这一变化,(可能是不正确的)程序可能不再被编译。可以直接这么修改:如果程序实际上是不正确的,就修复它,或者使用了违规的变量,例如把它赋值给空白标识符_。由于go vet总是指出这个错误,受影响的程序数量可能非常少。

Ports

AMD64

Go 1.18介绍了新的环境变量GOAMD64,它在编译时选择了AMD64架构的最小目标版本。允许的值是v1v2v3,或者v4。每一个更高的级别都需要并利用额外的处理器功能。详细的描述可以在这里找到。

GOAMD64环境变量默认是v1

RISC-V

Linux 上的RISC-V 64位架构 (the linux/riscv64 port) 现在支持c-archivec-shared构建模式。

Linux

Go 1.18需要Linux内核版本2.6.32或更高版本。

Windows

windows/arm and windows/arm64 ports现在支持非合作式抢占,从而使所有四个 Windows ports都具备了这种能力,这有望解决在调用 Win32 函数时遇到的长时间阻塞的微妙错误。

iOS

在iOS(ios/arm64 port)和基于AMD64的macOS(ios/amd64 port)上运行的iOS模拟器上,Go 1.18现在需要iOS 12或更高版本;对以前版本的支持已经停止了。

FreeBSD

Go 1.18是在FreeBSD 11.x上支持的最后一个版本,FreeBSD 11.x已经到了生命末期。Go 1.19 将需要 FreeBSD 12.2+ 或 FreeBSD 13.0+。FreeBSD 13.0+ 需要一个设置了 COMPAT_FREEBSD12 选项的内核(这是默认的)。

工具

Fuzzing

Go 1.18包含了the fuzzing提案描述的fuzzing的实现。

详见fuzzing landing page开始使用。

请注意的是fuzzing会消耗大量的内存,并且在运行时有可能会影响你机器的性能。还要注意的是,fuzzing引擎在运行时,会将扩大测试范围的数值写入$GOCACHE/fuzz内的fuzzing缓存目录,目前没有限制写入fuzzing缓存的文件数量或者写入的字节总数,所以它可能会占用大量的存储(有可能几个GBs)。

Go指令

go get

go get不再在模块感知模式下构建或安装软件包。go get现在用来调整go.mod里的依赖。实际上,标志-d会被一直启用。要在当前模块的上下文之外安装一个可执行文件的最新版本,使用go_install_example.com/cmd@latest,可以使用任何版本的查询来代替latest。在Go 1.16添加了这种形式的go install,所以支持旧版本的项目,可能需要提供go installgo get这两个安装指令说明。但在一个模块外使用的时候,go get现在会报告一个错误,因为那里没有go.mod文件来更新。在GOPATH模式下(GO111MODULE=off),go get依旧会和之前一样构建和安装包。

go.modgo.sum自动更新

go mod graph, go mod vendor, go mod verify, 和 go mod why 子命令将不会自动更新go.modgo.sum文件。(这些文件可以使用go getgo mod tidy,或者go mod download更新。)

go version

go命令现在可以在二进制文件中嵌入版本控制信息。 它包括当前签出的修订版,提交时间,以及表明是否存在已编辑或未跟踪的文件的标志。如果go命令是在Git、Mercurial、Fossil或Bazaar仓库中的一个目录中调用的,并且main包和其包含的main模块在同一个仓库中,那么版本控制信息就会被嵌入。这个信息可以通过标志-buildvcs=false省略。

此外,go命令还嵌入了关于构建的信息,包括构建和工具标签(用-tags设置),编译器、汇编器和链接器的标志(如-gcflags),cgo是否被启用,如果被启用,cgo环境变量的值(如CGO_CFLAGS)。 VCS和构建信息都可以使用go version -m文件或runtime/debug.ReadBuildInfo(针对当前运行的二进制文件)或新的debug/buildinfo包与模块信息一起读取。

嵌入的构建信息的底层数据格式可能会随着新的go版本的发布而改变,所以旧版本的go可能无法处理用新版本的go产生的构建信息。要从用go 1.18构建的二进制文件中读取版本信息,请使用go version命令和go 1.18以上版本的debug/buildinfo包。

go mod download

如果主模块的go.mod文件指定了go_1.17或更高版本,go mod download执行时不带参数,现在只下载主模块go.mod文件中明确要求的模块的源代码。(在go 1.17或更高版本的模块中,这个集合已经包括了构建主模块中的包和测试所需的所有依赖项)。要想同时下载跨平台依赖的源代码,请使用go mod download all

go mod vendor

go mod vendor 子命令现在支持一个 -o 标志来设置输出目录。(当使用 -mod=vendor 加载软件包时,其他 go 命令仍然从模块根部的vendor 目录中读取,所以这个标志的主要用途是用于需要收集软件包源代码的第三方工具。)

go mod tidy

go mod tidy 命令现在在 go.sum 文件中为那些需要源代码的模块保留了额外的校验和,以验证每个导入的软件包在构建列表中只由一个模块提供。因为这种情况比较少见,而且不应用它就会导致构建错误,所以这种改变以主模块的go.mod文件中的go版本为条件。

go work

go命令现在支持 “工作区 “模式。如果在工作目录或父目录中发现go.work文件,或者用GOWORK环境变量指定了一个,它将使go命令进入工作区模式。在工作区模式下,go.work文件将被用来确定作为模块解析根基的一组主模块,而不是使用通常找到的go.mod文件来指定单一的主模块。更多信息请见go work文件。

go build -asan

go build命令和相关命令现在支持一个-asan标志,它能够与用内存错误检测工具(C编译器选项`-fsanitize=address·)编译的C(或C++)代码进行互操作。

go test

go命令现在支持额外的命令行选项,用于上述新的fuzzing支持

  • go test支持-fuzz-fuzztime-fuzzminimizetime选项。关于这些的文档,请看go help testflag
  • go clean支持一个-fuzzcache选项。文档见go help testflag

//go:build lines

Go 1.17引入了//go:build方式,作为一种更易读的方式来写构建约束,而不是//+build的方式。从Go 1.17开始,gofmt添加//go:build行来匹配现有的+build行,并保持它们的同步,而go vet则在它们不同步的时候进行诊断。

由于Go 1.18的发布标志着对Go 1.16支持的结束,所有支持的Go版本现在都能理解//go:build的方式。在Go 1.18中,go fix现在删除了在go.mod文件中声明go 1.17或更高版本的模块中现已被淘汰的// +build行。

更多信息,见https://go.dev/design/draft-gobuild。

Gofmt

gofmt现在可以并发地读取和格式化输入文件,内存限制与GOMAXPROCS成正比。在有多个CPU的机器上,gofmt现在应该会明显加快。

Vet

对泛型的更新

vet工具被更新以支持泛型代码。在大多数情况下,只要在同等的非泛型代码中用其类型集合中的类型替代类型参数后,它就会报告泛型代码的错误。举个例子,如下vet会报告一个格式错误

  1. func Print[T ~int|~string](t T) {
  2. fmt.Printf("%d", t)
  3. }

因为它会在非泛型同等代码Print[string]报告一个格式错误

  1. func PrintString(x string) {
  2. fmt.Printf("%d", x)
  3. }

现有检查器精准度的提高

cmd/vet检查器copylockprintfsortslicetestinggoroutinetest都有适度的精度改进,以处理额外的代码模式。这可能会导致现有软件包中出现新的报告错误。例如,printf检查器现在跟踪由串联字符串常量创建的格式化字符串。所以vet会报告一个错误:

  1. // fmt.Printf formatting directive %d is being passed to Println.
  2. fmt.Println("%d"+` ≡ x (mod 2)`+"\n", x%2)

Runtime

当确定运行频率时,垃圾收集器现在包括垃圾收集器工作的非栈资源(例如,堆栈扫描)。 因此,当这些资源明显增多的情况下,垃圾收集器的开销就更可预测。对于大多数应用这些改变基本微乎其微;然而,有一些Go应用程序可能使用少量的内存,而花更多的时间在垃圾回收,或者反过来说,比以前更多。打算采取的解决方法是在必要时调整GOGC

现在,runtime更有效地将内存返回给操作系统,并因此而被调整为更积极地执行。

Go 1.17总体上改进了堆栈跟踪中参数的格式,但对于以寄存器传递的参数,可能会打印出不准确的值。在Go 1.18中,这一点得到了改进,在每个可能不准确的数值后面打印一个问号(?

编译器

Go 1.17实现了一种新的方法,即在选定的操作系统上的64位x86架构上使用寄存器而不是堆栈来传递函数参数和结果。Go 1.18扩展了支持的平台,包括64位ARM(GOARCH=arm64),大、小二进制的64位PowerPC(GOARCH=ppc64ppc64le),以及所有操作系统上的64位x86架构(GOARCH=amd64)。在64位ARM和64位PowerPC系统上,基准测试显示通常情况下性能改进为10%或以上。

正如Go 1.17发布说明中所提到的,这一变化不影响任何安全的Go代码的功能,并且旨在对大多数汇编代码没有影响。细节详见Go 1.17发布说明

编译器现在可以内联包含range循环或标记的for循环的函数。

新的-asan编译器选项支持新的go命令-asan选项。

因为编译器的类型检查器被全部替换为支持泛型,所以现在一些错误信息可能使用与以前不同的语法。 在某些情况下,Go 1.18之前的错误信息提供了更多的细节或以更有帮助的方式表述。我们打算在Go 1.19中解决这些情况。

由于编译器中与支持泛型有关的变化,Go 1.18的编译速度可能比Go 1.17的编译速度大约慢15%。编译后的代码的执行时间不受影响。我们打算在Go 1.19中提高编译器的速度。

链接器

链接器发出的重定位次数要少得多。因此,大多数代码库的链接速度会更快,只需要更少的内存来链接,并产生更小的二进制文件。处理Go二进制文件的工具应该使用Go 1.18的debug/gosym包来透明地处理新旧二进制文件。

新的-asan链接器选项支持新的go命令-asan选项。

Bootstrap

当从源码构建Go发行版且未设置GOROOT_BOOTSTRAP时,以前的Go版本会在$HOME/go1.4(Windows下为%HOMEDRIVE%HOMEPATH%\go1.4)目录下寻找Go 1.4或更高版本的引导工具链。Go现在首先寻找$HOME/go1.17$HOME/sdk/go1.17,然后再返回到$HOME/go1.4。我们打算在Go 1.19中要求Go 1.17或更高版本的bootstrap,这一改变应该能使过渡更加顺利。详情请见go.dev/issue/44505

核心库

新包debug/buildinfo

新的debug/buildinfo包提供了对模块版本、版本控制信息以及由go命令构建的可执行文件中嵌入的构建标志的访问。同样的信息也可以通过当前运行的二进制文件的runtime/debug.ReadBuildInfo和命令行的go version -m获得。

新包net/netip

新包net/netip定义了新的IP地址类型,Addr。与现有的net.IP类型相比,netip.Addr类型占用更少的内存,是不可改变的,并且具有可比性,所以它支持==,可以作为一个map的key使用。

除了Addr,软件包还定义了AddrPort,代表一个IP和端口,以及Prefix,代表一个网络CIDR前缀。

包也定义了几个方法来创建和校验这些新的类型: AddrFrom4, AddrFrom16, AddrFromSlice, AddrPortFrom, IPv4Unspecified, IPv6LinkLocalAllNodes, IPv6Unspecified, MustParseAddr, MustParseAddrPort, MustParsePrefix, ParseAddr, ParseAddrPort, ParsePrefix, PrefixFrom

net包包括与现有方法并行的新方法,但返回netip.AddrPort而不是更重的 net.IP*net.UDPAddr 类型: Resolver.LookupNetIP, UDPConn.ReadFromUDPAddrPort, UDPConn.ReadMsgUDPAddrPort, UDPConn.WriteToUDPAddrPort, UDPConn.WriteMsgUDPAddrPort。 新的UDPConn方法支持免分配的I/O。

net包现在还包括在现有的 TCPAddr/UDPAddr 类型和netip.AddrPort之间转换的函数和方法: TCPAddrFromAddrPort, UDPAddrFromAddrPort, TCPAddr.AddrPort, UDPAddr.AddrPort

TLS 1.0和1.1默认在客户端禁用

如果没有设置Config.MinVersion,它现在默认客户端连接为TLS 1.2。任何安全的最新服务器都应该支持TLS 1.2,而且浏览器自2020年以来就要求它。通过将Config.MinVersion设置为VersionTLS10,将仍然支持TLS 1.0和1.1。服务器端的默认值不变,为TLS1.0。

通过设置GODEBUG=tls10default=1环境变量,可以暂时将默认值恢复为TLS 1.0。这个选项将在 Go 1.19 中被删除。

拒绝SHA-1证书

crypto/x509现在将拒绝使用 SHA-1 哈希函数签名的证书。这并不适用于自签的根证书。自2017年以来,针对SHA-1的实际攻击已经被证明,自2015年以来,公开信任的证书颁发机构已经不再颁发SHA-1证书。

这可以通过设置GODEBUG=x509sha1=1环境变量暂时恢复。这个选项将在Go 1.19中被删除。

对库的微小改动

和往常一样,在考虑到Go 1的兼容性承诺的前提下,对库进行了各种小的修改和更新。