Go 1.20 主要变更清单

 提示:转载请注明原文链接

 本文永久链接:https://360us.net/article/83.html

语言变化

slice转数组

Go1.17在语言层面开始支持将slice转为指向数组的指针。

示例如下:

s := make([]byte, 2, 4)
// 将s这个slice转为指向byte数组的指针s0
// 其中[0]byte里的0表示数组的长度,虽然长度为0,但值不等于nil
s0 := (*[0]byte)(s)      // s0 != nil
fmt.Printf("%T")
// 将s[1:]这个slice转为指向byte数组的指针s1
// s1指向的数组的长度为1
s1 := (*[1]byte)(s[1:])  // &s1[0] == &s[1]
// 将s这个slice转为指向byte数组的指针s2
// s2指向的数组的长度为2 
s2 := (*[2]byte)(s)      // &s2[0] == &s[0]
// 将s这个slice转为指向byte数组的指针s4
// s4指向的数组的长度为4 
s4 := (*[4]byte)(s)      // panics: len([4]byte) > len(s)

注意:slice转为指向数组的指针时,如果数组定义的长度超过了slice的长度,会抛panic。

所以上面s4 := (*[4]byte)(s)这行代码虽然可以编译通过,但是会出现runtime panic。

Go 1.20之前不支持将slice直接转为数组,如果要转,得先转为指向数组的指针,再转为数组,如下面代码所示:

s := make([]byte, 2, 4)
s[0] = 100

s1 := (*[1]byte)(s[1:]) // &s1[0] == &s[1]
s2 := (*[2]byte)(s)     // &s2[0] == &s[0]
fmt.Printf("%T, %v, %p, %p\n", s1, s1[0], &s1[0], &s[1])
fmt.Printf("%T, %v, %v, %p\n", s2, s2[0], &s2[0], s)
// a1数组里元素的地址和s1指向的数组的元素地址不一样,a2同理
a1 := *s1
a2 := *s2
fmt.Printf("%T, %v, %p, %p\n", a1, a1[0], &a1[0], &s1[0])
fmt.Printf("%T, %v, %p, %p\n", a2, a2[0], &a2[1], &s2[1])

从Go 1.20开始,支持将slice直接转为数组,如下面代码所示:

s := make([]byte, 2, 4)
s[0] = 100
s1 := [1]byte(s[1:])
s2 := [2]byte(s)
// s1数组里元素的地址和s指向的数组的元素地址不一样,s2同理
fmt.Printf("%T, %v, %p, %p\n", s1, s1[0], &s1[0], &s[1])
fmt.Printf("%T, %v, %v, %p\n", s2, s2[0], &s2[0], s)


v := []string{"a", "b", "c", "c", "e", "f"}
s := [6]string(v)
fmt.Println(s)

总结:

slice转为指向数组的指针后,这个指针会指向和slice相同的地址空间
slice转为数组时,会把slice底层数组的值拷贝一份出来。转换后得到的数组的地址空间和slice底层数组空间不一样。

还有几个语法细节可以参考如下代码示例:

var t []string
t0 := [0]string(t)       // ok for nil slice t
t1 := (*[0]string)(t)    // t1 == nil
t2 := (*[1]string)(t)    // panics: len([1]string) > len(t)

u := make([]byte, 0)
u0 := (*[0]byte)(u)      // u0 != nil

slict转数组前提是切片和数字的长度和类型都要对的上。否则会出现如下报错:

panic: runtime error: cannot convert slice with length 5 to array or pointer to array with length 6

goroutine 1 [running]:
main.main()
	/tmp/sandbox1162344488/prog.go:9 +0x1d

Program exited.

Comparable类型

Go泛型里comparable这个类型约束(type constraint)有个坑,就是和Go语言里定义的可比较类型(Comparable types)并不一致。

什么是comparable types,简单来说就是可以用==和!=来进行比较的类型就是comparable types。

The equality operators == and != apply to operands that are comparable. The ordering operators <, <=, >, and >= apply to operands that are ordered.

有些可比较类型的变量不能作为类型实参(type argument)赋值给声明了comparable类型约束的类型参数(type parameter)。

例如Go语言说明里有如下这段内容:

Interface values are comparable. Two interface values are equal if they have identical dynamic types and equal dynamic values or if both have value nil.

这里明确指出,接口类型的值是可比较的,但是我们不能把2个interface作为类型实参给到类型参数。

参考如下代码示例:

package main

import "fmt"

func IsEqual[T comparable](a T, b T "T comparable") bool {
   return a == b
}

func main() {
   var a interface{} = 1
   var b interface{} = []int{1}
   fmt.Println(a == b) // false
   // go1.20之前的版本编译报错,go1.20开始支持
   fmt.Println(IsEqual(a, b)) 
}

对于上面最后一行代码,Go 1.20之前的版本编译报错。

$ go1.18 run example4.go
./example4.go:13:21: interface{} does not implement comparable

因为Go 1.20之前的版本认为空接口类型interface{}并没有实现comparable类型约束,不能作为类型实参传给类型参数。

从Go 1.20版本开始,不会编译报错,因为interface类型是comparable type,程序执行结果如下:

$ go1.20rc1 run example4.go
false
false

具体哪些类型是comparable type可以参考:Comparable types 里的说明。

unsafe包

Go 1.17版本在unsafe package里引入了Slice函数,如下所示:

func Slice(ptr *ArbitraryType, len IntegerType) []ArbitraryType

在Go 1.20版本里,标准库unsafe package定义了3个新的函数:

func SliceData(slice []ArbitraryType) *ArbitraryType
func String(ptr *byte, len IntegerType) string
func StringData(str string) *byte

如下函数签名:

  • func String(ptr *byte, len IntegerType) string:根据数据指针和字符长度构造一个新的 string。
  • func StringData(str string) *byte:返回指向该 string 的字节数组的数据指针。
  • func SliceData(slice []ArbitraryType) *ArbitraryType:返回该 slice 的数据指针。

新版本的用法:

func StringToBytes(s string) []byte {
    return unsafe.Slice(unsafe.StringData(s), len(s))
}

func BytesToString(b []byte) string {
    return unsafe.String(&b[0], len(b))
}

以往常用的 reflect.SliceHeaderreflect.StringHeader 将会被标注为被废弃。

值比较

Go语言说明现在明确指出结构体变量的值每次只比较一个字段,字段比较的顺序和字段在结构体里定义的顺序保持一致。

一旦某个字段的值比较出现不一致,就会马上停止比较。

以前的说明可能会让Go开发者有误解,以为结构体变量的比较需要比较所有字段,实际并不是。

类似的,数组的比较也是每次只比较一个元素,按照数组的下标索引由小到大逐个比较数组里每个元素的值。

这块只是改了说明而已,对大家的代码没有任何影响。

核心库变化

crypto/ecdh

Go 1.20新增了 crypto/ecdh 这个package,ecdh实现了Elliptic Curve Diffie-Hellman这个新的加密算法。

封装多个error

在原有 Go1.13 的 errors API 上进行新增和修改,核心是支持一个错误可以封装多个错误的特性。

package main

import (
 "errors"
 "fmt"
)

func main() {
 err1 := errors.New("err1")
 err2 := errors.New("err2")
 err := errors.Join(err1, err2)
 fmt.Printf("%T, %v\n", err, err)
 if errors.Is(err, err1) {
  fmt.Println("err is err1")
 }
 if errors.Is(err, err2) {
  fmt.Println("err is err2")
 }
 err3 := fmt.Errorf("error3: %w", err)
 fmt.Printf("%T, %v\n", err3, errors.Unwrap(err3))
 if errors.Is(err3, err1) {
  fmt.Println("err3 is err1")
 }
 if errors.Is(err3, err2) {
  fmt.Println("err3 is err2")
 }
}

输出:

*errors.joinError, err1
err2
err is err1
err is err2
*fmt.wrapError, err1
err2
err3 is err1
err3 is err2

fmt.Errorf里带有%w参数,就会返回一个实现了Unwrap方法的error类型的变量。

HTTP ResponseController

net/http这个package新增了名为ResponseController的新类型。

func RequestHandler(w ResponseWriter, r *Request) {
  rc := http.NewResponseController(w)
  rc.SetWriteDeadline(0) // disable Server.WriteTimeout when sending a large response
  io.Copy(w, bigData)
}

HTTP handler使用ResponseController来控制响应。

ResponseController不能在Handler.ServeHTTP返回之后使用。

Rewrite钩子函数

httputil.ReverseProxy[2] 类型新增了一个 Rewrite[3] 方法,这是一个钩子函数,用来取代之前的Director钩子函数。

proxyHandler := &httputil.ReverseProxy{
  Rewrite: func(r *httputil.ProxyRequest) {
    r.SetURL(outboundURL) // Forward request to outboundURL.
    r.SetXForwarded()     // Set X-Forwarded-* headers.
    r.Out.Header.Set("X-Additional-Header", "header set by the proxy")
  },
}

标准可的修改

  • bytes新增了 CutPrefixCutSuffix 函数,这2个函数功能上类似 TrimPrefixTrimSuffix ,但是还会返回一个bool类型的变量,表示这个string是否被修改了。新增了 Clone函数,会创建一个byte slice的拷贝。

  • encoding/binary, ReadVarintReadUvarint函数如果读的数据的值被损坏,比如只写了一部分内容,会返回 io.ErrUnexpectedEOF,而不是像之前返回io.EOF。

  • errors新的 Join函数可以把多个error变量的值组合在一起,封装为一个新的error变量。

  • fmt Errorf支持%w格式化字符串,可以返回一个实现了Unwrap方法的error类型变量。

  • strings新增了CutPrefixCutSuffix函数,这2个函数功能上类似 TrimPrefixTrimSuffix ,但是还会返回一个bool类型的变量,表示这个string是否被修改了。新增了 Clone函数,会创建一个string的拷贝。

  • sync Map 类型新增了3个新方法:SwapCompareAndSwapCompareAndDelete ,允许对已有的map做原子更新。

  • testing 新增了B.Elapsed 方法,可以返回当前的benchmark性能测试耗时了多久。

  • time新增了3个常量DateTimeDateOnlyTimeOnly,方便开发者做格式转换,不用在代码里写死"2006-01-02 15:04:05"。新增Compare方法,func (t Time) Compare(u Time) int,将tu 两者进行比较。

 评论
暂无评论