Go 1.20新变化!第一部分:语言特性

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

又到了 Go 发布新版本的时刻了

2022 年第一季度的 Go 1.18 是一个主版本它在语言中增加了期待已久的泛型同时还有许多微小功能更新[1]与优化[2]。2022 年第三季度的 Go 1.19 是一个比较低调[3]的版本。

现在是 2023 年Go 1.20 RC 版本[4]已经发布而正式版本也即将到来Go 团队已经发布了版本说明草案[5]。

在我看来Go 1.20 的影响介于 1.18 和 1.19 之间比 1.19 有更多的功能更新并解决了一些长期存在的问题但没有达到 1.18 中为语言增加泛型这样的重磅规模。

尽管如此我还是要把我对“Go 1.20 的新变化”的看法分成系列三篇博文。

首先我写了 Go 1.20 中的语言变化如下在下一篇文章中我将写标准库的重要变化最后一篇将讲解 Go 1.20 中我最喜欢的对标准库的小改动。


那么让我们来看看语言方面的变化。首先对泛型的规则做了一个小小的修改。有了 Go 泛型你可以通过一个函数获取任何map的键

func keys[K comparable, V any](m map[K]V) []K {
    var keys []K
    for k := range m {
        keys = append(keys, k)
    }
    return keys
}
···

在这段代码中K comparable, V any 为“类型约束”。这意味着 K 可以是任何 comparable 的类型而 V 则没有类型限制。comparable 类型为数字、布尔、字符串和由 comparable 元素组成的固定大小的复合类型等。因此K 为 intV 为一个 bytes 切片是合法的但 K 是一个 bytes 切片是非法的。

我说过上面的代码会给你任何 map 的键但在 Go 1.18 和 1.19 中这并不是完全正确的。如果你试图把它用在一个键值为接口类型的 map 上它将不会被编译。

m := make(map[any]any) // ok
keys(m)
// 编译器错误Go 1.19any 没有实现 comparable
···

这个问题归结为围绕 K comparable 含义的解读。要作为 map 键使用类型必须被 Go 编译器认为是 comparable 的。例如这是无效的

m := make(map[func()]any)
// 编译器错误无效的 map 键类型 func()
···

然而你可以通过使用接口来得到一个运行时错误而不是编译器错误

m := make(map[any]any) // 正确
k := func() {}
m[k] = 1 // panic运行时错误哈希值为不可哈希的类型 func()

所以像 any 这样的接口类型是 map 的有效键类型但如果你试图把一个缺少有效类型定义的键放到 map 中就会在运行时出现 panic 错误。显然没有人希望他们的代码在运行时出现 panic 错误但这是在 map 中允许动态类型键的唯一方法。

下面是一个从不同角度看同一问题的例子。假设我有一个这样的错误类型

type myerr func() string

func (m myerr) Error() string {
    return m()
}

而现在我想使用自定义的错误类型进行比较

var err1 error = myerr(func() string { return "err1" })
var err2 error = myerr(func() string { return "err2" })
fmt.Println(err1 != nil, err2 != nil)  // true true

fmt.Println(err1 == err2)
// panic运行时错误对 main.myerr 不可比类型进行比较

正如你所看到的一个接口值在编译时被认为是 comparable 类型但是如果它被赋的值是一个“不可比类型”则在运行时就会出现 panic。如果你试图比较两个 http.Handler而它们恰好都是 http.HandlerFuncs你同样可以看到这个问题。

当 Go 1.18 支持了泛型后大家发现[6]由于接口在编译时被认为是 但可能会包含不可比较的具体类型。如果你写的泛型代码的类型约束是 comparable但错误的值被存储在一个接口中就有可能出现运行时 panic。保守起见Go 团队决定[7]在评估此特性的全部影响阶段Go 1.18 限制使用接口作为 comparable 类型。

现在已经过了一年了也发布了两个版本经过大量在 Github 上进行的冗长讨论[8]Go 团队认为在通用代码中使用接口作为 comparable 类型应该是足够安全的。如果你在 Go 1.20 中运行 keys(map[any]any{}) 它可以正常运行你不必考虑上面的任何说明。


Go 1.20 中的另一个语言变化更容易解释。如果你有一个切片现在你可以很容易地将其转换为一个固定长度的数组

s := []string{"a", "b", "c"}
a := [3]string(s)

如果切片比数组短你会因越界而产生 panic

s := []int{1, 2, 3}
a := [4]int(s)
// panic: 运行时错误: 不能将长度为 3 的切片转换成长度为 4 的数组或数组指针

这源于 Go 1.17 中增加的数组指针转换特性

s := []string{"a", "b", "c"}
p := (*[3]string)(s)

在这种情况下p 指向 s 定义的数组因此修改一个就会修改另一个

s := []string{"a", "b", "c"}
p := (*[3]string)(s)
s[0] = "d"
p[1] = "e"
fmt.Println(s, p) // [d e c] &[d e c]

另一方面随着 Go 1.20 中新增的切片转换为数组特性数组是 切片内容的副本

s := []string{"a", "b", "c"}
a := [3]string(s)
s[0] = "d"
a[1] = "e"
fmt.Println(s, a)
// [d b c] [a e c]

除了将切片转换为数组的语法外Go 1.20 还为处理切片数据的 unsafe 包带来了一些新增内容。

reflect 包一直有 reflect.SliceHeader[9] 和 reflect.StringHeader[10] 它们是 Go 中切片和字符串的运行时表示:

type SliceHeader struct {
    Data uintptr
    Len  int
    Cap  int
}

type StringHeader struct {
    Data uintptr
    Len  int
}

reflect.SliceHeader 和 reflect.StringHeader 都有一个 Warning 提示“它的表示方法可能在以后的版本中改变因此不能确保障安全或可移植”并且在试图废除它们[11]。误用这些类型可能会导致代码崩溃[12]但是在实践中很多程序都依赖于类似这样的切片布局很难想象 Go 团队会在没有大量警告的情况下改变它因为很多程序会崩溃。

为了给 Gopher 们提供一种官方支持的编写不安全代码的方式Go 1.17 增加了unsafe.Slice[13]它允许你把任何指针变成一个切片不管是否是个好主意。

obj := struct{ x, y, z int }{1, 2, 3}
slice := unsafe.Slice(&obj.x, 3)
obj.x = 4
slice[1] = 5
fmt.Println(obj, slice)
// {4 5 3} [4 5 3]

在 Go 1.20 中还有 unsafe.SliceData[14]它返回一个指向切片数据的指针unsafe.String[15]它以不安全的方式通过一个 byte 指针创建字符串以及 unsafe.StringData[16]它以不安全的方式返回一个指向字符串数据的指针。

这些字符串函数是额外增加的不安全方式因为它们允许你违反 Go 的字符串不可变规则但它也给了你很大的控制权可以在不分配新内存的前提下转换 byte 切片。

这些工具像利刃一样好用却很容易割伤自己。在语言中直接支持这些工具可能更好而不是仅仅让大家使用 unsafe.Pointer 来祈祷它奏效。

用 Hank Hill 的话来形容“无论你做什么你都应该以正确的方式去做即使是错误的事情。[17]”

相关链接

[1] https://blog.carlmjohnson.net/post/2021/golang-118-minor-features/

[2] https://blog.carlmjohnson.net/post/2022/golang-118-even-more-minor-features/

[3] https://blog.carlmjohnson.net/post/2022/golang-119-new-features/

[4] https://groups.google.com/g/golang-nuts/c/HMUAm5j5raw/m/va3dxBFyAgAJ

[5] https://tip.golang.org/doc/go1.20

[6] https://github.com/golang/go/issues/49587

[7] https://github.com/golang/go/issues/50646

[8] https://github.com/golang/go/issues/51338

[9] https://pkg.go.dev/reflect#SliceHeader

[10] https://pkg.go.dev/reflect#StringHeader

[11] https://go-review.googlesource.com/c/go/+/401434

[12] https://github.com/golang/go/issues/40701

[13] https://pkg.go.dev/unsafe#Slice

[14] https://pkg.go.dev/unsafe@go1.20rc2#SliceData

[15] https://pkg.go.dev/unsafe@go1.20rc2#String

[16] https://pkg.go.dev/unsafe@go1.20rc2#StringData

[17] https://www.getyarn.io/yarn-clip/08e52ddd-63ee-429b-b40c-b12c8ff6670b

原文地址

https://blog.carlmjohnson.net/post/2023/golang-120-language-changes/

原文作者

Carl M. Johnson

本文永久链接https://github.com/gocn/translator/blob/master/2023/w02_golang_120_language_changes.md

译者pseudoyu

校对小超人

往期推荐

20abf193c0db207e540d857f0035b0cf.jpeg

「每周译Go」了解 Go 中的 init

eca8eb5192030c5294bd963132078303.jpeg

系统设计技巧使用Postgres作为发布/订阅和作业服务器

d11916af2ad136e47908403b03493cee.jpeg

一文读懂 Go Http Server 原理

想要了解Go更多内容欢迎扫描下方👇关注公众号回复关键词 [实战群]  ,就有机会进群和我们进行交流

9aa6c9bf6e308d544ea3294bb8f61784.png

分享、在看与点赞Go 3f0553963e9354383a3e75e8875df312.gif

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: go

“Go 1.20新变化!第一部分:语言特性” 的相关文章