目录
发布历史
go1.20稳定版在2023-02-01正式发布了,秉持着不安装最新版本的原则,我将go由1.17升级为1.19版本。
顺手也将goland升级了下:
GO 1.18
一如既往,该版本保持了Go 1的兼容性。(As always, the release maintains the Go 1 promise of compatibility.)
我们希望几乎所有的Go程序都能像以前一样继续编译和运行。(We expect almost all Go programs to continue to compile and run as before.)
具体来说,包含以下几大特性:
- 泛型(Generics)
- 模糊测试(Fuzzing)
- 工作空间(Workspaces)
- TryLock()方法,虽然TryLock的正确使用确实存在,但它们是罕见的,而且使用TryLock的使用往往是mutex在特定使用中更深层次问题的标志。(GO官方多年来最终妥协的产物)
- 20% 性能提升:Apple M1、ARM64 和 PowerPC64 用户开心了!由于 Go 1.17 的寄存器 ABI 调用约定扩展到这些架构,Go 1.18 包括高达 20% 的 CPU 性能改进。
- go-get不再以模块感知模式构建或安装软件包。go-get现在致力于调整go.mod中的依赖关系。要在当前模块的上下文之外安装可执行文件(exe)的最新版本,请使用go install example.com/cmd@latest。
- Go 1.18明确了能修改go.mod、go.sum的命令只有三个:go get、go mod tidy和go mod download。这样开发人员就可以放心的在项目根目录下执行Go工具链提供的其他命令了。
- 新的net/nip包…
泛型
Therefore, while we encourage the use of generics where it makes sense, please use appropriate caution when deploying generic code in production.(因此,尽管我们鼓励在有意义的地方使用泛型,但在生产中部署泛型代码时请谨慎。)
引入
- 假设有两个求和函数SumInts 和 SumFloats并且逻辑差不多。
- 如果将来有其他类型,我们必须增加额外的函数,代码逻辑也类似。
- 有了泛型,只需要一个函数就可以实现以上两个函数的功能,而且可以方便扩展为支持其他相关类型,比如 map[int]float64 等。
func SumIntsOrFloats[K comparable, V int | float64](m map[K]V) V {
var s V
for _, v := range m {
s += v
}
return s
}
函数 SumIntsOrFloats 声明了两种参数:类型参数和普通函数参数。其中类型参数放在 [] 中,普通参数依然放在 () 中。
该函数的类型参数是:K comparable, V int | float64,其中 K、V 的名字不重要,分别表示某种类型,comparable 和 int | float64 是 K、V 类型的约束,即调用该方法时,K、V 允许的类型。comparable 是语言预定义的约束,即表示所有可比较类型,也就是说,K 可以是任意可比较类型。
而 V 的类型约束 int | float64 表示只允许是 int 或 float64,其他类型编译会报错。
该函数的普通参数:m map[K]V,这表明,m 是一个 map,它的 key 类型是 K,value 类型是 V。很显然,这两个是该函数「类型参数」定义的类型。
在 main 中增加如下调用:(同一个函数,支持 map[string]int 和 map[string]float64。)
fmt.Printf("泛型计算结果,Ints 结果: Floats 结果: %v\n", SumIntsOrFloats[string, int](ints), SumIntsOrFloats[string, float64](floats))
或
fmt.Printf("泛型计算结果,Ints 结果: Floats 结果: %v\n", SumIntsOrFloats(ints), SumIntsOrFloats(floats))
实际上 Go 会进行类型推断,即编译器会通过普通参数的类型推导出「类型参数」。不过,跟 Go 中其他类型自动推导类似,有些情况是无法自动推导的,这时候必须手动指定实际的类型参数。
概念
Go还引入了非常多全新的概念:
- 类型形参 (Type parameter)
- 类型实参(Type argument)
- 类型形参列表( Type parameter list)
- 类型约束(Type constraint)
- 实例化(Instantiations)
- 泛型类型(Generic type)
- 泛型接收器(Generic receiver)
- 泛型函数(Generic function)
声明类型约束
上面没有将 int | float64 定义为一个命名约束,相当于约束字面量(或联合类型)。一般有两种场景会单独声明类型约束:
- 约束太长,比如有很多类型,直接写在函数中,会严重影响可读性
- 方便类型约束重用
将上面 V 的约束定义为单独的类型约束:(实际是接口,但不能作为单独类型使用)
type Number interface{
int | float64
}
func SumNumbers[K comparable, V Number](m map[K]V) V {
var s V
for _, v := range m {
s += v
}
return s
}
Constraint(约束)
约束的意思是限定范围, constraint的作用就是限定范围, 将T限定在某种范围内
而常用的范围, 自然会想到的有:
- an(interface{})
- Interger(所有int)
type Integer interface {
Signed | Unsigned
}
type Signed interface {
~int | ~int8 | ~int16 | ~int32 | ~int64
}
- Float(所有Float)
- comparable(所有可以比较的类型, 我们可以给所有可以比较的类型定制一些方法)
…
这些约束, 不是被官方定义为内置类型, 就是被涵盖在了constraints包内。
其中~
主要用来表示底层类型一致,例如type MyInt int 和int底层都是int类型,如果不使用~
,那么类型实例化时就不能使用MyInt类型。
泛型实例
类型形参的互相套用
任何泛型类型都必须传入类型实参实例化才可以使用
type WowStruct[T int | float32, S []T] struct {
Data S
MaxValue T
MinValue T
}
var ws WowStruct[int, []int]
// 泛型类型 WowStuct[T, S] 被实例化后的类型名称就叫 WowStruct[int, []int]
经过实例化之后 WowStruct[T,S] 的定义类似如下:
// 一个存储int类型切片,以及切片中最大、最小值的结构体
type WowStruct[int, []int] struct {
Data []int
MaxValue int
MinValue int
}
因为 S 的定义是 []T ,所以 T 一定决定了的话 S 的实参就不能随便乱传了,下面这样的代码是错误的:
// 错误。S的定义是[]T,这里T传入了实参int, 所以S的实参应当为 []int 而不能是 []float32
ws := WowStruct[int, []float32]{
Data: []float32{1.0, 2.0, 3.0},
MaxValue: 3,
MinValue: 1,
}
几种语法错误
-
定义泛型类型的时候,基础类型不能只有类型形参,如下:
// 错误,类型形参不能单独使用 type CommonType[T int|string|float32] T // 正确的定义CommonType type CommonType interface{ int|string|float32 } func Sum[T CommonType](number T)bool{ }
-
当类型约束的一些写法会被编译器误认为是表达式时会报错。如下:
//✗ 错误。T *int会被编译器误认为是表达式 T乘以int,而不是int指针 type NewType[T *int] []T // 上面代码再编译器眼中:它认为你要定义一个存放切片的数组,数组长度由 T 乘以 int 计算得到 type NewType [T * int][]T //✗ 错误。和上面一样,这里不光*被会认为是乘号,| 还会被认为是按位或操作 type NewType2[T *int|*float64] []T //✗ 错误 type NewType2 [T (int)] []T
为了避免这种误解,解决办法就是给类型约束包上 interface{} 或加上逗号消除歧义.
type NewType[T interface{*int}] []T type NewType2[T interface{*int|*float64}] []T // 如果类型约束中只有一个类型,可以添加个逗号消除歧义 type NewType3[T *int,] []T //✗ 错误。如果类型约束不止一个类型,加逗号是不行的 type NewType4[T *int|*float32,] []T
因为上面逗号的用法限制比较大,推荐统一用 interface{} 解决问题。
特殊的泛型类型(无意义)
type SB[T int | string] int
var a SB[int] = 123 // 编译正确
var b SB[string] = 123 // 编译正确
var c SB[string] = "hello" // 编译错误,因为"hello"不能赋值给底层类型int
因为类型定义是type SB[T int|string] int
,所以无论传入什么类型实参,实例化后的新类型的底层类型都是 int 。所以int类型的数字123可以赋值给变量a和b,但string类型的字符串 “hello” 不能赋值给c。
泛型类型的嵌套
// 定义泛型类型 Slice[T]
type Slice[T int|string|float32|float64] []T
// ✗ 错误。泛型类型Slice[T]的类型约束中不包含uint, uint8
type UintSlice[T uint|uint8] Slice[T]
// ✓ 正确。基于泛型类型Slice[T]定义了新的泛型类型 FloatSlice[T] 。FloatSlice[T]只接受float32和float64两种类型
type FloatSlice[T float32|float64] Slice[T]
// ✓ 正确。基于泛型类型Slice[T]定义的新泛型类型 IntAndStringSlice[T]
type IntAndStringSlice[T int|string] Slice[T]
// ✓ 正确 基于IntAndStringSlice[T]套娃定义出的新泛型类型
type IntSlice[T int] IntAndStringSlice[T]
// 在map中套一个泛型类型Slice[T]
type WowMap[T int|string] map[string]Slice[T]
// 在map中套Slice[T]的另一种写法
type WowMap2[T Slice[int] | Slice[string]] map[string]T
类型约束的两种选择
type WowStruct[T int|string] struct {
Name string
Data []T
}
或
type WowStruct2[T []int|[]string] struct {
Name string
Data T
}
// 这种情况的时候,使用前一种写法会更好:
type WowStruct3[T int | string] struct {
Data []T
MaxValue T
MinValue T
}
这里推荐全用第一种,提高可读性。
匿名结构体不支持泛型
// 只能这样用
testCase := struct {
caseName string
got int
want int
}{
caseName: "test OK",
got: 100,
want: 100,
}
泛型接收者
type MySlice[T int | float32] []T
func (s MySlice[T]) Sum() T {
var sum T
for _, value := range s {
sum += value
}
return sum
}
var s MySlice[int] = []int{1, 2, 3, 4}
fmt.Println(s.Sum()) // 输出:10
Go的方法并不支持泛型。
但是因为receiver支持泛型, 所以如果想在方法中使用泛型的话,通过receiver使用类型形参:
type A[T int | float32 | float64] struct {
}
// 方法可以使用类型定义中的形参 T
func (receiver A[T]) Add(a T, b T) T {
return a + b
}
// 用法:
var a A[int]
a.Add(1, 2)
var aa A[float32]
aa.Add(1.0, 2.0)
型类型定义的变量不能使用类型断言
func (receiver Queue[T]) Put(value T) {
// Printf() 可输出变量value的类型(底层就是通过反射实现的)
fmt.Printf("%T", value)
// 通过反射可以动态获得变量value的类型从而分情况处理
v := reflect.ValueOf(value)
switch v.Kind() {
case reflect.Int:
// do something
case reflect.String:
// do something
}
// ...
}
为了避免使用反射而选择了泛型,结果到头来又为了一些功能在在泛型中使用反射,是不是真的需要用泛型?
泛型函数
func Add[T int | float32 | float64](a T, b T) T {
return a + b
}
和泛型类型一样,泛型函数也是不能直接调用的,要使用泛型函数的话必须传入类型实参之后才能调用。
Add[int](1,2) // 传入类型实参int,计算结果为 3
Add[float32](1.0, 2.0) // 传入类型实参float32, 计算结果为 3.0
Add[string]("hello", "world") // 错误。因为泛型函数Add的类型约束中并不包含string
Go还支持类型实参的自动推导:
Add(1, 2) // 1,2是int类型,编译请自动推导出类型实参T是int
Add(1.0, 2.0) // 1.0, 2.0 是浮点,编译请自动推导出类型实参T是float32
匿名函数不支持泛型。
变得复杂的接口
Go支持将类型约束单独拿出来定义到接口中,从而让代码更容易维护,
type IntUintFloat interface {
int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32 | uint64 | float32 | float64
}
type Slice[T IntUintFloat] []T
接口嵌套:
type Int interface {
int | int8 | int16 | int32 | int64
}
type Uint interface {
uint | uint8 | uint16 | uint32
}
type Float interface {
float32 | float64
}
type Slice[T Int | Uint | Float] []T // 使用 '|' 将多个接口类型组合
type SliceElement interface {
Int | Uint | Float | string // 组合了三个接口类型并额外增加了一个 string 类型
}
type Slice[T SliceElement] []T
~
指定底层类型
var s1 Slice[int] // 正确
type MyInt int
var s2 Slice[MyInt] // ✗ 错误。MyInt类型底层类型是int但并不是int类型,不符合 Slice[T] 的类型约束
泛型类型 Slice[T] 允许的是 int 作为类型实参,而不是 MyInt (虽然 MyInt 类型底层类型是 int ,但它依旧不是 int 类型)。
Go新增了一个符号 ~ ,在类型约束中使用类似 ~int 这种写法的话,就代表着不光是 int ,所有以 int 为底层类型的类型也都可用于实例化。
type Int interface {
~int | ~int8 | ~int16 | ~int32 | ~int64
}
type Uint interface {
~uint | ~uint8 | ~uint16 | ~uint32
}
type Float interface {
~float32 | ~float64
}
type Slice[T Int | Uint | Float] []T
var s Slice[int] // 正确
type MyInt int
var s2 Slice[MyInt] // MyInt底层类型是int,所以可以用于实例化
type MyMyInt MyInt
var s3 Slice[MyMyInt] // 正确。MyMyInt 虽然基于 MyInt ,但底层类型也是int,所以也能用于实例化
type MyFloat32 float32 // 正确
var s4 Slice[MyFloat32]
- ~后面的类型不能为接口
- ~后面的类型必须为基本类型
接口的定义
在Go1.18之前,Go官方对 接口(interface) 的定义是:接口是一个方法集(method set)
Go1.18开始就是依据这一点将接口的定义正式更改为了 类型集(Type set)
类型约束 指定了类型形参可接受的类型集合,只有属于这个集合中的类型才能替换形参用于实例化。
那么从Go1.18开始 接口实现(implement) 的定义自然也发生了变化:
当满足以下条件时,我们可以说 类型 T 实现了接口 I ( type T implements interface I):
- T 不是接口时:类型 T 是接口 I 代表的类型集中的一个成员 (T is an element of the type set of I)
- T 是接口时: T 接口代表的类型集是 I 代表的类型集的子集(Type set of T is a subset of the type set of I)
类型的并集
type Uint interface { // 类型集 Uint 是 ~uint 和 ~uint8 等类型的并集
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64
}
类型的交集
type AllInt interface {
~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint32
}
type Uint interface {
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64
}
type A interface { // 接口A代表的类型集是 AllInt 和 Uint 的交集
AllInt
Uint
}
type B interface { // 接口B代表的类型集是 AllInt 和 ~int 的交集
AllInt
~int
}
空集
没有任何一种类型属于空集
type Bad interface {
int
float32
} // 类型 int 和 float32 没有相交的类型,所以接口 Bad 代表的类型集为空
空接口和 any
空接口代表了所有类型的集合,
- 虽然空接口内没有写入任何的类型,但它代表的是所有类型的集合,而非一个 空集
- 类型约束中指定 空接口 的意思是指定了一个包含所有类型的类型集,并不是类型约束限定了只能使用 空接口 来做类型形参
所以,go1.18将interface{}
定义为了any
所以从 Go 1.18 开始,所有可以用到空接口的地方其实都可以直接替换为any,
# 将当前目录下边的所有文件中的interface{}替换为any
gofmt -w -r 'interface{} -> any' ./...
comparable(可比较) 和 可排序(ordered)
- comparable 比较容易引起误解的一点是很多人容易把他与可排序搞混淆。
- 可比较指的是 可以执行 != == 操作的类型,并没确保这个类型可以执行大小比较( >,<,<=,>= )
虽然可以直接使用官方包 golang.org/x/exp/constraints ,但因为这个包属于实验性质的 x 包,今后可能会发生非常大变动,所以并不推荐直接使用
接口两种类型
type ReadWriter interface {
~string | ~[]rune
Read(p []byte) (n int, err error)
Write(p []byte) (n int, err error)
}
// 类型 StringReadWriter 实现了接口 Readwriter
type StringReadWriter string
func (s StringReadWriter) Read(p []byte) (n int, err error) {
// ...
}
func (s StringReadWriter) Write(p []byte) (n int, err error) {
// ...
}
// 类型BytesReadWriter 没有实现接口 Readwriter
type BytesReadWriter []byte
func (s BytesReadWriter) Read(p []byte) (n int, err error) {
...
}
func (s BytesReadWriter) Write(p []byte) (n int, err error) {
...
}
接口类型 ReadWriter 代表了一个类型集合,所有以 string 或 []rune 为底层类型,并且实现了 Read() Write() 这两个方法的类型都在 ReadWriter 代表的类型集当中。
Go1.18开始将接口分为了两种类型:
- 基本接口(Basic interface):接口定义中如果只有方法的话,那么这种接口被称为基本接口(Basic interface)。(1.18之前版本)
- 一般接口(General interface):如果接口内不光只有方法,还有类型的话,这种接口被称为 一般接口(General interface) ,一般接口类型不能用来定义变量,只能用于泛型的类型约束中。(1.18+)
泛型接口
-
形式1
type DataProcessor[T any] interface { Process(oriData T) (newData T) Save(data T) error } type CSVProcessor struct { } // 注意,方法中 oriData 等的类型是 string func (c CSVProcessor) Process(oriData string) (newData string) { .... } func (c CSVProcessor) Save(oriData string) error { ... } // CSVProcessor实现了接口 DataProcessor[string] ,所以可赋值 var processor DataProcessor[string] = CSVProcessor{} processor.Process("name,age\nbob,12\njack,30")
-
形式2
type DataProcessor2[T any] interface { int | ~struct{ Data interface{} } Process(data T) (newData T) Save(data T) error } // JsonProcessor 实现了接口 DataProcessor2[string] 的两个方法,同时底层类型是 struct{ Data interface{} }。所以实现了接口 DataProcessor2[string] type JsonProcessor struct { Data interface{} } func (c JsonProcessor) Process(oriData string) (newData string) { } func (c JsonProcessor) Save(oriData string) error { } // 错误。DataProcessor2[string]是一般接口不能用于创建变量 var processor DataProcessor2[string] // 正确,实例化之后的 DataProcessor2[string] 可用于泛型的类型约束 type ProcessorList[T DataProcessor2[string]] []T // 正确,接口可以并入其他接口 type StringProcessor interface { DataProcessor2[string] PrintString() } // 错误,带方法的一般接口不能作为类型并集的成员 type StringProcessor interface { DataProcessor2[string] | DataProcessor2[[]byte] PrintString() }
只有实现了 Process(string) string 和 Save(string) error 这两个方法,并且以 int 或 struct{ Data interface{} } 为底层类型的类型才算实现了这个接口
一般接口(General interface) 不能用于变量定义只能用于类型约束,所以接口 DataProcessor2[string] 只是定义了一个用于类型约束的类型集
接口定义的限制规则
- 用 | 连接多个类型的时候,类型之间不能有相交的部分(即必须是不交集),但是相交的类型中是接口的话,则不受这一限制:
type MyInt int
// 错误,MyInt的底层类型是int,和 ~int 有相交的部分
type _ interface {
~int | MyInt
}
type MyInt int
type _ interface {
~int | interface{ MyInt } // 正确
}
- 类型的并集中不能有类型形参
type MyInf[T ~int | ~string] interface {
~float32 | T // 错误。T是类型形参
}
- 接口不能直接或间接地并入自己
type Bad interface {
Bad // 错误,接口不能直接并入自己
}
- 接口的并集成员个数大于一的时候不能直接或间接并入 comparable 接口
type OK interface {
comparable // 正确。只有一个类型的时候可以使用 comparable
}
type Bad1 interface {
[]int | comparable // 错误,类型并集不能直接并入 comparable 接口
}
- 带方法的接口(无论是基本接口还是一般接口),都不能写入接口的并集中:
type _ interface {
~int | ~string | error // 错误,error是带方法的接口(一般接口) 不能写入并集中
}
type DataProcessor[T any] interface {
~string | ~[]byte
Process(data T) (newData T)
Save(data T) error
}
// 错误,实例化之后的 DataProcessor[string] 是带方法的一般接口,不能写入类型并集
type _ interface {
~int | ~string | DataProcessor[string]
}
type Bad[T any] interface {
~int | ~string | DataProcessor[T] // 也不行
}
小结
- 泛型应用范围没有想象中的那么大,比如想写个队列,写个链表、栈、堆之类的数据结构可以用泛型:
// 这里类型约束使用了空接口,代表的意思是所有类型都可以用来实例化泛型类型 Queue[T] (关于接口在后半部分会详细介绍)
type Queue[T interface{}] struct {
elements []T
}
// 将数据放入队列尾部
func (q *Queue[T]) Put(value T) {
q.elements = append(q.elements, value)
}
- 泛型提高了代码编写的复杂度,是接口的一场革命,并且也会降低执行效率(泛型推导)。
- 听官方的劝,别乱用泛型。
工作空间workspace
工作区模式(Workspace mode),可不是之前 GOPATH 时代的 Workspace,而是希望在本地开发时支持多 Module。
有如下目录结构:
当go build
或go run
时,显示没有找到syncMap。
go: finding module for package github.com/Generalzy/common/syncMap
解决方法1:添加replace选项(这时候goland已经提示,可以将syncMap加入到工作区)
解决方法2:工作区模式
通过 go help
和go help work
可以看到,新增了 work 相关的命令:
到最外边初始化工作区:
go work init 子模块1 子模块2 ...
注意几点:
- 多个子模块应该在一个目录下。(这不是必须的,但更好管理,否则 go work init 需要提供正确的子模块路径)
- go work init 需要在最外边的目录执行;
- go work init 之后跟上需要本地开发的子模块目录名;
删除掉mod中的replace语句,依旧可以正常执行。
注意,go.work 不需要提交到 Git 中,因为它只是你本地开发使用的。
如果想要禁用 workspace,可以通过 -workfile=off 实现,比如:go run -workfile=off main.go 或 go build -workfile=off,这样运行又报错了。但通过这种方式,可以验证依赖包提交到 github 上之后的情况。
go work use 添加新的模块到工作区
go work use 模块目录3
添加一个模块到工作区
go 1.19
use (
./hello
./example
./模块目录3
)
// replaces 命令与 go.mod 指令相同,用于替换项目中依赖的仓库地址
replace (
github.com/link1st/example => ./example
)
go work edit
用于编辑 go.work 文件,可以使用 edit 命令编辑和手动编辑 go.work 文件效果是相同的.
go work edit -fmt go.work 重新格式化 go.work 文件
go work edit -replace=github.com/link1st/example=./example go.work 替换代码模块
go work edit -dropreplace=github.com/link1st/example 删除替换代码模块
go work edit -use=./example go.work 添加新的模块到工作区
go work edit -dropuse=./example go.work 从工作区中删除模块
go work sync
将工作区的构建列表同步到工作区的模块
全局禁用
export GOWORK=off
结论
go.work 文件优先级高于 go.mod 中定义在同时使用 go.work 和 go.mod replace 功能的的时候分别指定不同的代码仓库路径,go.work 优先级高于 go.mod 中定义.
TryLock
有时我们希望尝试获取锁,如果获取到了则继续执行,如果获取不到,我们也不想阻塞住,而是去调用其它的逻辑,这个时候我们就想要 TryLock 方法:即尝试获取锁,获取不到也不堵塞。
这个需求,2013 年就有人提出,但官方没有采纳。2018 年又有人提出也没有下文,直到 2021 年 4 月,有人再次提出,同时也给出了标准库中需要的场景。
对此go的负责人rsc认为 TryLock 会鼓励设计者对锁进行不精确的思考,这可能最终会成为 race(竞态) 的根源。同时,他认为仅为 http2 提供 TryLock 不值得,希望有更具说服力的案例。
最终1.18版本给出了3个TryLock方法,不过,rsc 建议,大家尽量别使用它。
Go 1.19
在本次 Go1.19 的新版本更新中,新特性是比较少的。其中主要的原因还是泛型的各项工作给 Go 团队带来了不少的工作量。它的大部分变化在于工具链、运行时和库的实现。
Go 1.19 重要的变化有:
- Go 内存模型.
- 随着内存模型的更新,Go 1.19 在 sync/atomic 包引入了新的类型:types Bool, Int32, Int64, Uint32, Uint64, Uintptr 和 Pointer 等,这让原子值的使用更方便。
- 现在,编译器使用跳转表来实现大整数和字符串的switch语句。对于switch语句的性能改进因情况而异,但可能达到快约20%。 (仅适用于GOARCH=amd64和GOARCH=arm64)
- 工具方面,文档格式增强。文档注释中添加了对链接、列表和更清晰标题的支持。
- runtime 方面,最值得关注的变化就是增加了 runtime/debug.SetMemoryLimit,可以限制 Go 的内存使用。
- 从 Go1.19 起,构建约束 unix 现在可以在 //go:build 行中被识别,能够起到配套的约束作用。
- 在 Go 1.19 起增加了对 Linux 上 Loongson 64 位架构的支持(GOOS=linux,GOARCH=loong64)。
- 包的改动…
Go 1.20
最新的 Go 版本 1.20 在Go 1.19发布六个月后发布。它的大部分变化在于工具链、运行时和库的实现。
- 垃圾收集器经过重组,从而减少了内存使用量并将性能提高了 2%。
- Go 构建 1.20 比 1.19 快 20%。这意味着对最近的速度降低感到失望的人们可以重新享受构建 Go 应用程序的速度。
- "Go 1.17增加了从切片到数组指针的转换。Go 1.20将其扩展,允许从切片到数组的转换:给定一个切片x,现在可以写成[4]byte(x),而不是*(*[4]byte)(x)。
- unsafe包定义了三个新函数SliceData、String和StringData。加上Go 1.17的Slice函数,这些函数现在提供了完整的能力来构造和拆解切片和字符串值,而不依赖于它们的确切表示。
- 规范现在定义了结构体值按字段一次比较,考虑字段在结构体类型定义中出现的顺序,并在第一次不匹配时停止。先前可能已阅读规范,好像在第一次不匹配之后需要比较所有字段。同样,规范现在定义了数组值按递增的索引顺序逐个比较。在这两种情况下,差异影响是否必须触发某些比较。现有程序保持不变:新的规范措辞描述了实现始终以来的行为。
- 可比较的类型(例如普通接口)现在可以满足可比较的约束,即使类型参数不是严格可比较的(比较可能在运行时引发panic)。这使得可以用非严格可比较的类型参数(例如接口类型或包含接口类型的复合类型)实例化受可比较约束的类型参数,例如用户定义的通用映射键的类型参数。"
- Go 1.20 是可在 Windows 7、8、Server 2008 和 Server 2012 的任何版本上运行的最后一个版本。Go 1.21 至少需要 Windows 10 或 Server 2016。
Go 1.21
- 最新的 Go 版本 1.21 比Go 1.20发布六个月后发布。它的大部分变化在于工具链、运行时和库的实现。
- Go 1.21 为该语言添加了三个新的内置函数。
- 新函数min和max计算max固定数量的给定参数的最小(或最大,对于 )值。
- 新函数clear删除映射中的所有元素或将切片的所有元素归零。
- 现在可以更精确地指定包初始化顺序。新算法是:
- 按导入路径对所有包进行排序。
- 重复直到包列表为空:
- 找到列表中所有导入均已初始化的第一个包。
- 初始化该包并将其从列表中删除。
- Go 1.21包含了我们考虑在未来版本中进行的一项语言更改的预览:将for循环变量更改为每次迭代而不是每个循环,以避免意外的共享错误。
- 新的log/slog包:新的log/slog包提供带有级别的结构化日志记录。结构化日志记录以键值对形式发出,以实现对大量日志数据的快速、准确处理。该包支持与流行的日志分析工具和服务集成。
- 新的testing/slogtest包:新的testing/slogtest包可帮助验证slog.Handler实现。
- 新的slices包:新的slices包提供许多对切片执行的常见操作,使用通用函数,这些函数适用于任何元素类型的切片。
- 新的maps包:新的maps包提供对映射执行的几个常见操作,使用通用函数,这些函数适用于任何键或元素类型的映射。
- 新的cmp包:新的cmp包定义了类型约束Ordered和两个新的通用函数Less和Compare,这些函数在有序类型中非常有用。
10.新的WithoutCancel函数返回原始上下文被取消时不被取消的上下文的副本。新的WithDeadlineCause和WithTimeoutCause函数提供了在截止日期或计时器过期时设置上下文取消原因的方式。可以使用Cause函数检索原因。新的AfterFunc函数注册一个在上下文被取消后运行的函数。
min&max
内置函数min和max分别计算有序类型的固定数量参数的最小值或最大值。必须至少有一个参数。
与运算符相同的类型规则适用:对于有序参数x和y,如果x + y有效,则min(x, y)有效,而min(x, y)的类型是x + y的类型(max同理)。如果所有参数都是常量,则结果也是常量。
var x, y int
m := min(x) // m == x
m := min(x, y) // m is the smaller of x and y
m := max(x, y, 10) // m is the larger of x and y but at least 10
c := max(1, 2.0, 10) // c == 10.0 (floating-point kind)
f := max(0, float32(x)) // type of f is float32
var s []string
_ = min(s...) // invalid: slice arguments are not permitted
t := max("", "foo", "bar") // t == "foo" (string kind)
对于数字参数,假设所有 NaN 都相等,min和max是可交换的和结合的:
min(x, y) == min(y, x)
min(x, y, z) == min(min(x, y), z) == min(x, min(y, z))
对于浮点参数的负零、NaN 和无穷大,适用以下规则:
x y min(x, y) max(x, y)
-0.0 0.0 -0.0 0.0 // negative zero is smaller than (non-negative) zero
-Inf y -Inf y // negative infinity is smaller than any other number
+Inf y y +Inf // positive infinity is larger than any other number
NaN y NaN NaN // if any argument is a NaN, the result is a NaN
对于字符串参数,min的结果是具有最小值(或max的结果是最大值)的第一个参数,按字节逐字典比较:
min(x, y) == if x <= y then x else y
min(x, y, z) == min(min(x, y), z)
clear
内置函数clear
接受一个map、切片或类型参数类型的参数,并删除或将所有元素归零。
Call Argument type Result
clear(m) map[K]T deletes all entries, resulting in an
empty map (len(m) == 0)
clear(s) []T sets all elements up to the length of
s to the zero value of T
clear(t) type parameter 参考下文
如果clear的参数类型是type parameter,则其类型集中的所有类型必须是映射或切片,并且clear执行与实际类型参数对应的操作。
如果映射或切片为nil,则clear不执行任何操作。