目录
Go 1.18简介
语言的变化
泛型
Bug修复
各端
AMD64
RISC-V
Linux
Windows
iOS系统
FreeBSD
工具类
模糊测试
Go命令
Gofmt
Vet
Runtime
编译器
链接器
Bootstrap
核心库
新的debug/buildinfo包
新的net/netip包
TLS 1.0和1.1默认在客户端禁用
拒绝 SHA-1 证书
对库的细微改动
最新的Go版本,即1.18版,是一个重要的版本,包括对语言、工具链的实现、运行时和库的改变。Go 1.18 在Go 1.17[1]的七个月后到来。一如既往,该版本保持了 Go 1的兼容性承诺[2]。我们期望几乎所有的Go程序都能像以前一样继续编译和运行。
Go 1.18 包括类型参数提案[3]所描述的泛型功能。这包括对语言的主要 - 但完全向后兼容的改变。
这些新的语言变化需要大量的新代码,这些代码还没有在生产环境中进行过大量的测试。这只会随着越来越多的人编写和使用泛型代码而发生。我们相信这个功能实现得很好,质量很高。然而,与Go的大多数方面不同,我们无法用现实世界的经验来支持这一信念。因此,虽然我们鼓励在有意义的地方使用泛型,但在生产中部署泛型代码时请适当谨慎。
虽然我们相信新的语言特性设计得很好,而且规定得很清楚,但我们有可能犯了错误。我们想强调的是Go 1 的兼容性保证[4]说:"如果有必要解决规范中的不一致或不完整,解决这个问题可能会影响现有程序的意义或合法性。我们保留解决此类问题的权利,包括更新实现方式"。它还说:"如果一个编译器或库有一个违反规范的bug,如果这个bug被修复,一个依赖这个bug行为的程序可能会被破坏。我们保留修复这种错误的权利"。换句话说,有可能会有一些使用泛型的代码在1.18版本中可以使用,但在以后的版本中会被破坏。我们不计划也不期望做任何这样的改变。然而,在未来的版本中,由于我们今天无法预见的原因,破坏1.18版本的程序可能成为必要。我们将尽可能地减少任何这样的破坏,但我们不能保证破坏会是零。
下面是一个最明显的变化清单。如需更全面的概述,请参见 提案[5]。详情请见语言规范[6]。
~
。union
和~T
类型元素。这样的接口只能用作类型约束。接口现在定义了一组类型以及一组方法。any
是空接口的别名。它可以用来代替 interface{}
.comparable
是一个接口,它表示所有类型的集合,可以使用 ==
或 !=
进行比较。它只能作为(或嵌入)类型约束使用。有三个使用泛型的实验性包可能是有用的。这些包在x/exp
库中;它们的API不在Go 1的保证范围内,随着我们对泛型的经验积累,可能会发生变化。
对泛型代码有用的约束条件,例如 `constraints.Ordered`[15].
对任何元素类型的切片进行操作的泛型函数的集合。
一组泛型函数,可以对任何键或元素类型的map
进行操作。
目前的泛型实现有以下已知的限制。
Go编译器不能处理泛型函数或方法中的类型声明。我们希望在 Go 1.19 中提供对该功能的支持。
Go编译器不接受预先声明的函数real
、imag
和complex
的参数类型的参数。我们希望在 Go 1.19 中取消这一限制。
Go编译器只支持在参数类型为P的值x上调用方法m,如果m是由P的约束接口明确声明的。同样,方法值x.m
和方法表达式P.m
也只有在P明确声明了m的情况下才会被支持,尽管由于P中的所有类型都实现了m,m可能在P的方法集中。我们希望在Go 1.19中取消这一限制。
Go编译器不支持访问结构字段x.f
,其中x是类型参数类型,即使类型参数的类型集中的所有类型都有字段f。我们可能会在Go 1.19中取消这一限制。
不允许将类型参数或指向类型参数的指针嵌入结构体类型中的未命名字段。同样地,在接口类型中嵌入类型参数也是不允许的。目前还不清楚这些是否会被允许。
有一个以上术语的联合元素不能包含一个具有非空方法集的接口类型。这是否会被允许,目前还不清楚。
泛型也代表了Go生态系统的一个巨大变化。虽然我们已经更新了几个支持泛型的核心工具,但还有很多事情要做。剩下的工具、文档和库都需要时间来赶上这些语言的变化。
Go 1.18编译器现在可以正确地告告declared but not used
在函数字面内设置但从未使用的变量的声明但未使用错误。在 Go 1.18 之前,编译器在这种情况下不会报告错误。这修复了长期存在的编译器问题 #8560[18]。由于这一变化,(可能是不正确的)程序可能无法再编译了。必要的修正是简单直接的:如果程序确实不正确,就修正它,或者使用违规的变量,例如把它赋值给空白标识符_
。由于go vet
总是指出这个错误,受影响的程序数量可能非常小。
Go 1.18 编译器现在报告说,当把'1' << 32'
等rune
常量表达式作为参数传给预先声明的函数 print
和 println
时,会出现溢出,这与用户定义的函数的行为一致。在Go 1.18之前,编译器在这种情况下并不报错,而是默默地接受这种常量参数,如果它们适合于int64
。由于这一变化,(可能是不正确的)程序可能无法再编译了。必要的修正是很简单直接的:如果程序确实不正确,就修正它,或者明确地将违规的参数转换为正确的类型。由于 go vet
总是指出这个错误,受影响的程序数量可能非常少。
Go 1.18 引入了新的 GOAMD64
环境变量,它可以在编译时选择 AMD64 架构的最小目标版本。允许的值是 v1、v2、v3 或 v4。每一个更高的级别都需要并利用额外的处理器特性。详细的描述可以在这里[19]找到。
GOAMD64
环境变量的默认值是v1。
Linux 上的 64 位 RISC-V 架构(linux/riscv64
)现在支持 c-archive
和 c-shared
构建模式。
Go 1.18 需要 Linux 内核版本 2.6.32 或更高版本。
windows/arm
和 windows/arm64
现在支持非合作性抢占,从而使所有四个 Windows端都具备了这种能力,这有望解决在调用 Win32
函数时遇到的长时间阻塞的微妙错误。
在 iOS (ios/arm64
) 和基于 AMD64 的 macOS (ios/amd64
) 上运行的 iOS 模拟器上,Go 1.18 现在需要 iOS 12 或更高版本;对以前版本的支持已经停止了。
Go 1.18 是在 FreeBSD 11.x 上支持的最后一个版本,该版本已经达到了生命末期。Go 1.19 将需要 FreeBSD 12.2+ 或 FreeBSD 13.0+。FreeBSD 13.0+ 将需要一个设置了 COMPAT_FREEBSD12
选项的内核(这是默认的)。
Go 1.18 包含了 fuzzing
提案所描述的 fuzzing 实现。
请参阅 fuzzing 页面[20]以开始使用。
请注意,fuzzing 会消耗大量内存,在运行时可能会影响机器的性能。 还要注意的是,模糊引擎在运行时将扩大测试范围的数值写入$GOCACHE/fuzz
内的模糊缓存目录。目前对写入模糊缓存的文件数量或总字节数没有限制,所以它可能会占用大量的存储空间(可能是几个GB)。
go get
不再以模块感知模式构建或安装软件包。现在,go get
专门用于调整go.mod
中的依赖关系。有效地,-d
标志总是被启用。要在当前模块的上下文之外安装一个可执行文件的最新版本,使用 go install example.com/[email protected]
。任何版本的查询[21]都可以用来代替最新版本latest
。这种形式的 go install
是在 Go 1.16 中添加的,因此支持旧版本的项目可能需要同时提供 go install
和 go get
的安装说明。go get
现在在模块外使用时报告错误,因为没有 go.mod
文件需要更新。在 GOPATH
模式下(GO111MODULE=off
),go get
仍然像以前一样构建和安装软件包。
go mod graph
、go mod vendor
、go mod verify
和go mod why
子命令不再自动更新go.mod
和go.sum
文件。(这些文件可以通过go get
、go mod tidy
或go mod download
明确更新)。
go 命令现在会在二进制文件中嵌入版本控制信息。它包括当前签出的修订版、提交时间,以及一个指示是否存在已编辑或未跟踪文件的标志。 如果go命令是在Git、Mercurial、Fossil或Bazaar仓库的一个目录中调用的,并且主包和其包含的主模块在同一个仓库中,则会嵌入版本控制信息。这个信息可以用标志-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版本命令和go 1.18以上版本的debug/buildinfo
包。
如果主模块的 go.mod
文件指定了 go 1.17 或更高版本,go mod download
不带参数,现在只下载主模块的 go.mod
文件中明确要求的模块的源代码。(在 go 1.17 或更高版本的模块中,这组模块已经包括了构建主模块中的包和测试所需的所有依赖项)。要想同时下载跨平台依赖的源代码,请使用go mod download all
。
go mod vendor
子命令现在支持 -o
标志来设置输出目录。(当使用 -mod=vendor
加载软件包时,其他 go 命令仍然从模块根部的 vendor
目录读取,所以这个标志主要用于需要收集软件包源代码的第三方工具。)
go mod tidy
命令现在在 go.sum
文件中为那些需要源代码的模块保留了额外的校验和,以验证每个导入的软件包在构建列表[22]中只由一个模块提供。因为这种情况很罕见,而且不应用它就会导致构建错误,所以这一变化不以主模块的go.mod
文件中的go版本为条件。
go 命令现在支持 "工作区 "模式。 如果在工作目录或父目录中发现go.work
文件,或者使用GOWORK
环境变量指定一个,它将使go命令进入工作区模式。在工作区模式下,go.work
文件将被用来确定作为模块解析根的一组主模块,而不是使用通常找到的go.mod
文件来指定单一的主模块。更多信息见go work
文档。
go build
命令和相关命令现在支持一个-asan
标志,能够与用AddressSanitizer(C编译器选项-fsanitize=address
)编译的C(或C++)代码互操作。
go 命令现在支持额外的命令行选项,用于上述新的模糊测试支持。
go test
支持 -fuzz,
-fuzztime
, 和 -fuzzminimizetime
选项。关于这些选项的文档请参见 go help testflag
。go clean
支持 -fuzzcache
选项。文档见go help clean
。
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
现在可以并发地读取和格式化输入文件,其内存限制与 GOMAXPROCS
成正比。在有多个 CPU 的机器上,gofmt
现在应该会明显加快。
vet
工具已更新以支持泛型代码。在大多数情况下,只要在非泛型代码中用其类型集中的类型替换类型参数后,它就会报告泛型代码的错误。例如,在以下情况下,vet
会报告一个格式错误
func Print[T ~int|~string](t T "T ~int|~string") {
fmt.Printf("%d", t)
}
因为它将在Print[string]
的非泛型等价物中报告一个格式错误。
func PrintString(x string) {
fmt.Printf("%d", x)
}
cmd/vet
检查器、copylock
、printf
、sortslice
、testinggoroutine
和tests
都有适度的精度改进,以处理额外的代码模式。这可能会导致现有软件包中出现新的报告错误。例如,printf
检查器现在跟踪由串联字符串常量创建的格式化字符串。所以vet
会在以下情况下报告一个错误。
// fmt.Printf 格式化指令 %d 被传递给 Println。
fmt.Println("%d "+` ≡x (mod 2)`+"\n", x%2)
垃圾回收器现在在确定运行频率时包括垃圾回收器工作的非堆源(例如堆栈扫描)。因此,当这些来源很重要时,垃圾回收器的开销就更容易预测。对于大多数应用程序来说,这些变化可以忽略不计;然而,一些Go应用程序现在可能使用更少的内存,而在垃圾回收上花费更多的时间,或者相反,比以前更多。预定的解决方法是在必要时对GOGC
进行调整。
运行时现在更有效地将内存返回到操作系统,并因此被调整为更积极地工作。
Go 1.17总体上改进了堆栈跟踪中参数的格式,但对于以寄存器传递的参数可能会打印出不准确的值。Go 1.18中对此进行了改进,在每个可能不准确的值后面打印一个问号?
内置函数append
在决定必须分配一个新的底层数组时,现在使用了一个稍有不同的公式来增长一个切片。新的公式不太容易出现分配行为的突然转变。
Go 1.17 实现了一种新的方法,即在选定的操作系统上,在 64 位 x86 架构上使用寄存器而不是堆栈传递函数参数和结果。Go 1.18扩展了支持的平台,包括64位ARM(GOARCH=arm64
)、大端和小端 64位PowerPC(GOARCH=ppc64,ppc64le
),以及所有操作系统上的64位x86架构(GOARCH=amd64
)。在64位ARM和64位PowerPC系统上,基准测试显示典型的性能提升10%或更多。
正如 Go 1.17 发布说明中所提到的,这一变化不影响任何安全 Go 代码的功能,并且旨在对大多数汇编代码没有影响。更多细节请参见 Go 1.17 发行说明[23]。
编译器现在可以内联包含range
循环或标记的 for 循环的函数。
新的 -asan
编译器选项支持新的 go 命令 -asan
选项。
由于编译器的类型检查器被全部替换为支持泛型,一些错误信息现在可能使用与以前不同的措辞。在某些情况下,Go 1.18之前的错误信息提供了更多的细节或以更有帮助的方式来表述。我们打算在Go 1.19中解决这些情况。
由于编译器中与支持泛型有关的变化,Go 1.18的编译速度可能比Go 1.17的编译速度大约慢15%。编译后的代码的执行时间不受影响。我们打算在Go 1.19中提高编译器的速度。
链接器发出的重定位要少得多[24]。因此,大多数代码库的链接速度会更快,需要更少的内存来链接,并生成更小的二进制文件。处理 Go 二进制文件的工具应使用 Go 1.18 的 debug/gosym
包来透明地处理新旧二进制文件。
新的 -asan
链接器选项支持新的 go 命令 -asan
选项。
当从源代码构建 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 中要求使用 1.17 或更高版本的引导程序,这一变化应使过渡更加顺利。详情请见go.dev/issue/44505[25]。
新的debug/buildinfo
包提供了对模块版本、版本控制信息以及嵌入到go命令所构建的可执行文件中的构建标志的访问。同样的信息也可以通过当前运行的二进制文件的runtime/debug.ReadBuildInfo
和命令行上的go version -m
获得。
新的net/netip
包定义了一个新的IP地址类型,Addr。与现有的 net.IP
类型相比,netip.Addr
类型占用更少的内存,是不可变的,并且是可比较的,因此它支持 ==
并可以作为一个map键使用。
除了 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
。
如果没有设置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中被移除。
crypto/x509
现在将拒绝使用 SHA-1 哈希函数签名的证书。这并不适用于自签的根证书。自2017年以来,针对SHA-1的实际攻击已经被证明,自2015年以来,公开信任的证书颁发机构已经不再颁发SHA-1证书。
这可以通过设置GODEBUG=x509sha1=1
环境变量暂时恢复。这个选项将在Go 1.19中被移除。
这里生态君对几个觉得重要常用的包的改动做了翻译。
像往常一样,在考虑到Go 1的兼容性的前提下,对库进行了各种细微的修改和更新。
新的Writer.AvailableBuffer
方法返回一个空的缓冲区,其容量可能不是空的,用于类似append的API。在追加之后,该缓冲区可以提供给后续的Write
调用,并可能避免任何复制。
Reader.Reset
和Writer.Reset
方法在对空缓冲区的对象调用时,现在使用默认的缓冲区大小。
新的Cut
函数将一个[]byte
通过分隔符切割。它可以取代并简化许多Index
、IndexByte
、IndexRune
和SplitN
的常见用法。
Trim
、TrimLeft
和TrimRight
现在是无内存分配的,特别是对于小的ASCII切割集,速度可提高10倍。
Title
函数现在已经废弃了。它不能处理Unicode标点符号和特定语言的大小写规则,并且被golang.org/x/text/cases
包所取代。
根据提案,为支持参数化函数和类型,对 go/ast
和 go/token
增加了以下内容,并对 go/ast
包进行了补充。
FuncType
和 TypeSpec
节点有一个新的字段 TypeParams
来保存类型参数(如果有的话)。IndexListExpr
表示具有多个索引的索引表达式,用于具有一个以上显式类型参数的函数和类型实例化。新的Config.GoVersion
字段设置接受的Go语言版本。
根据提案,为支持类型参数,在 go/types
包中增加了以下内容。
增加了新的 TypeParam
类型、工厂函数 NewTypeParam
和相关方法来表示类型参数。
新类型 TypeParamList
保存一个类型参数的列表。
新类型 TypeList
保存一个类型的列表。
新的工厂函数NewSignatureType
分配了一个带有(receiver
或函数)类型参数的签名。为了访问这些类型参数,Signature
类型有两个新方法Signature.RecvTypeParams
和Signature.TypeParams
。
Named
类型有四个新的方法。Named.Origin
用于获取实例化类型的原始参数化类型,Named.TypeArgs
和Named.TypeParams
用于获取实例化或参数化类型的类型参数或类型参数,Named.SetTypeParams
用于设置类型参数(例如,当导入一个命名类型时,由于可能的循环周期,命名类型的分配和类型参数的设置不能同时进行)。
interface
类型有四个新方法。Interface.IsComparable
和Interface.IsMethodSet
用于查询接口定义的类型集的属性,Interface.MarkImplicit
和Interface.IsImplicit
用于设置和测试接口是否是围绕类型约束字的隐式接口。
增加了新的类型Union
和Term
、工厂函数NewUnion
和NewTerm
以及相关方法,以表示接口中的类型集。
新的函数Instantiate
实例化了一个参数化的类型。
新的Info.Instances
映射通过新的Instance类型记录函数和类型实例化。
增加了新的 ArgumentError
类型和相关方法,以表示与类型参数有关的错误。
增加了新的 Context
类型和工厂函数 NewContext
,以便通过新的 Config.Context
字段,在类型检查的包之间共享相同的类型实例。
谓词 AssignableTo
、ConvertibleTo
、Implements
、Identical
、IdenticalIgnoreTags
和 AssertableTo
现在也适用于属于或包含泛型接口的参数,即在 Go 代码中只能作为类型约束使用的接口。请注意,AssignableTo
、ConvertibleTo
、Implements
和 AssertableTo
的行为在参数为非实例化泛型的情况下是未定义的,如果第一个参数是泛型接口,AssertableTo
是未定义的。
新的Value.SetIterKey
和Value.SetIterValue
方法使用一个map迭代器作为源来设置一个Value。它们等同于Value.Set(iter.Key())
和Value.Set(iter.Value())
,但做的内存分配较少。
新的 Value.UnsafePointer
方法将Value
的值作为一个unsafe.Pointer
返回。这允许调用者从Value.UnsafeAddr
和Value.Pointer
迁移,以消除在调用点执行uintptr
到unsafe.Pointer
转换的需要(因为unsafe.Pointer
规则要求)。
新的MapIter.Reset
方法改变了它的receiver
,在不同的map
上进行迭代。使用MapIter.Reset
可以对许多map
进行无内存分配的迭代。
一些方法(Value.CanInt
, Value.CanUint
, Value.CanFloat
, Value.CanComplex
)被添加到Value
中以测试转换是否安全。
Value.FieldByIndexErr
已被添加到 Value.FieldByIndex
中,以避免在通过一个指向嵌入式结构的空指针时发生panic
。
reflect.Ptr
和 reflect.PtrTo
分别被重命名为 reflect.Pointer
和 reflect.PointerTo
,以便与 reflect
包的其他部分保持一致。旧名称将继续工作,但在未来的 Go 版本中会被废弃。
BuildInfo
结构有两个新字段,包含关于二进制文件如何构建的额外信息。
GoVersion
保存用于构建二进制文件的 Go 版本。Settings
是BuildSettings
结构的一个slice,持有描述构建过程的键/值对。新的Cut
函数将一个字符串围绕着分隔符进行切割。它可以取代并简化许多Index
、IndexByte
、IndexRune
和SplitN
的常见用法。
新的Clone
函数复制了输入字符串,而返回的克隆字符串没有引用输入字符串的内存。
Trim
、TrimLeft
和TrimRight
现在是0内存分配的,特别是对于小的ASCII切割集,速度可提高10倍。
Title
函数现在已经废弃了。它不能处理 Unicode 标点符号和特定语言的大写规则,并且被 golang.org/x/text/cases
包所取代。
新的方法Mutex.TryLock
、RWMutex.TryLock
和RWMutex.TryRLock
,将在当前未持有锁的情况下获取锁。
增加了-run
和 -bench
参数中的 / 的优先级。A/B|C/D 曾被视为 A/(B|C)/D,现在被视为 (A/B)|(C/D)。
如果-run
选项没有选择任何test,-count
选项将被忽略。这可能会改变现有测试的行为,在不太可能的情况下,一个测试改变了每次运行测试函数本身时的子测试集。
新的 testing.F
类型被上述新的 fuzzing 支持所使用。测试现在也支持命令行选项 -test.fuzz
, -test.fuzztime
, 和 -test.fuzzminimizetime
。
Go 1.18 在Go 1.17: https://go.dev/doc/go1.17
[2]的兼容性承诺: https://go.dev/doc/go1compat
[3]Go 1.18 包括类型参数提案: https://go.googlesource.com/proposal/+/refs/heads/master/design/43651-type-parameters.md
[4]Go 1 的兼容性保证: https://go.dev/doc/go1compat
[5]提案: https://go.googlesource.com/proposal/+/refs/heads/master/design/43651-type-parameters.md
[6]语言规范: https://go.dev/ref/spec
[7]函数: https://go.dev/ref/spec#Function_declarations
[8]类型声明: https://go.dev/ref/spec#Type_declarations
[9]类型参数: https://go.dev/ref/spec#Type_parameter_declarations
[10]操作符和标点符号: https://go.dev/ref/spec#Operators_and_punctuation
[11]接口类型: https://go.dev/ref/spec#Interface_types
[12]预声明标识符: https://go.dev/ref/spec#Predeclared_identifiers
[13]预声明标识符: https://go.dev/ref/spec#Predeclared_identifiers
[14]golang.org/x/exp/constraints
: https://pkg.go.dev/golang.org/x/exp/constraints
constraints.Ordered
: https://pkg.go.dev/golang.org/x/exp/constraints#Ordered
golang.org/x/exp/slices
: https://pkg.go.dev/golang.org/x/exp/slices
golang.org/x/exp/maps
: https://pkg.go.dev/golang.org/x/exp/maps
#8560: https://golang.org/issue/8560
[19]这里: https://golang.org/wiki/MinimumRequirements#amd64
[20]fuzzing 页面: https://go.dev/doc/fuzz/
[21]任何版本的查询: https://go.dev/ref/mod#version-queries
[22]构建列表: https://go.dev/ref/mod#glos-build-list
[23]Go 1.17 发行说明: https://go.dev/doc/go1.17#compiler
[24]重定位要少得多: https://tailscale.com/blog/go-linker/
[25]go.dev/issue/44505: https://go.dev/issue/44505