设为首页 加入收藏

TOP

golang1.21新特性速览(一)
2023-08-26 21:10:42 】 浏览:153
Tags:golang1.21

经过了半年左右的开发,golang 1.21 在今天早上正式发布了。

这个版本中有不少重要的新特性和变更,尤其是在泛型相关的代码上。

因为有不少大变动,所以建议等第一个patch版本也就是1.21.1出来之后再进行升级,以免遇到一些意外的bug带来麻烦。

好了,一起来看看1.21带来的新特性吧。

本文索引

新的内置函数

1.21添加了三个新的内置函数:minmaxclear

minmax如其字面意思,用了选出一组变量里(数量大于等于1,只有一个变量的时候就返回那个变量的值)最大的或者最小的值。两个函数定义是这样的:

func min[T cmp.Ordered](x T, y ...T) T
func max[T cmp.Ordered](x T, y ...T) T

注意那个类型约束,这是新的标准库里提供的,原型如下:

type Ordered interface {
    ~int | ~int8 | ~int16 | ~int32 | ~int64 |
    ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr |
    ~float32 | ~float64 |
    ~string
}

也就是说只有基于所有除了map,chan,slice以及复数之外的基本类型的变量才能使用这两个函数。或者换句话说,只有可以使用<><=>===!=进行比较的类型才可以使用min和max。

有了min和max,可以把许多自己手写的代码替换成新的内置函数,可以少写一些帮助函数。而且使用新的内置函数还有一个好处,在变量个数比较少的时候还有编译器的优化可用,比自己写min函数性能上要稍好一些。

使用上也很简单:

maxIntValue := max(0, 7, 6, 5, 4, 3, 2, 1) // 7 type int
minIntValue := min(8, 7, 6, 5, 4, 3, 2, 1) // 1 type int

目前max和min都不支持slice的解包操作:max(1, numbers...)

对于clear来说事情比min和max复杂。clear只接受slice和map,如果是对泛型的类型参数使用clear,那么类型参数的type set必须是map或者slice,否则编译报错。

clear的定义如下:

func clear[T ~[]Type | ~map[Type]Type1](t T)

对于参数是map的情况,clear会删除所有map里的元素(不过由于golang的map本身的特性,map存储的数据会被正常销毁,但map已经分配的空间不会释放):

func main() {
        m := map[int]int{1:1, 2:2, 3:3, 4:4, 5:5}
        fmt.Println(len(m))  // 5
        clear(m)
        fmt.Println(len(m))  // 0
}

然而对于slice,它的行为又不同了:会把slice里所有元素变回零值。看个例子:

func main() {
        s := make([]int, 0, 100) // 故意给个大的cap便于观察
        s = append(s, []int{1, 2, 3, 4, 5}...)
        fmt.Println(s) // [1 2 3 4 5]
        fmt.Println(len(s), cap(s)) // len: 5; cap: 100
        clear(s)
        fmt.Println(s) // [0 0 0 0 0]
        fmt.Println(len(s), cap(s)) // len: 5; cap: 100
}

这个就比较反直觉了,毕竟clear首先想到的应该是把所有元素删除。那它的意义是什么呢?对于map来说意义是很明确的,但对于slice来说就有点绕弯了:

slice的真实大小是cap所显示的那个大小,如果只是用s := s[:0]来把所有元素“删除”,那么这些元素实际上还是留在内存里的,直到s本身被gc回收或者往s里添加新元素把之前的对象覆盖掉,否则这些对象是不会被回收掉的,这一方面可以提高内存的利用率,另一方面也会带来泄露的问题(比如存储的是指针类型或者包含指针类型的值的时候,因为指针还存在,所以被指向的对象始终有一个有效的引用导致无法被回收),所以golang选择了折中的办法:把所有已经存在的元素设置成0值

如果想安全的正常删除slice的所有元素,有想复用slice的内存,该怎么办?答案是:

s := make([]T, 0, 100) // 故意给个大的cap便于观察
s = append(s, []T{*new(T), *new(T)}...)

clear(s)
s = s[:0]

如果没有内置函数clear,那么我们得自己循环一个个赋值处理。而有clear的好处是,编译器会直接用memset把slice的内存里的数据设置为0,比循环会快很多。有兴趣的可以看看clear在slice上的实现:代码在这

类型推导更加智能

其实就是bug修复,以前类似这样的代码在某些情况下没法正常进行推导:

func F[T ~E[], E any](t T, callable func(E))

func generic[E any](e E) {}
F(t, generic) // before go1.21: error; after go1.21: ok

理论上只要能推导出E的类型,那么Fgeneric的所有类型参数都能推导出来,哪怕generic本身是个泛型函数。以前想正常使用就得这么写:F(t, generic[Type])

所以与其说是新特性,不如说是对类型推导的bug修复。

针对类型推导还有其他一些修复和报错信息的内容优化,但这些都没上面这个变化大,所以恕不赘述。

panic的行为变化

1.21开始如果goroutine里有panic,那么这个goroutine里的defer里调用的recover必然不会返回nil值。

这导致了一个问题:recover的返回值是传给panic的参数的值,panic(nil)这样的代码怎么办?

先要提醒一下,panic(nil)本身是无意义的,且会导致recover的调用方无法判断究竟发生了什么,所以一直是被各类linter包括go vet命令警告的。然而这么写语法上完全正确,所以只有警告并不能解决问题。

解决办法是,如果现在使用panic(nil)或者panic(值为nil的接口),recover会收到一个新类型的error:*runtime.PanicNilError

总体上算是解决了问题,然而它把有类型的但值是nil的接口也给转换了,虽然从最佳实践的角度来讲panic一个空值的接口是不应该的,但多少还是会给使用上带来一些麻烦。

所以目前想要禁用这一行为的话,可以设置环境变量:export GODEBUG=panicnil=1。如果go.mod里声明的go版本小于等于

首页 上一页 1 2 3 下一页 尾页 1/3/3
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇Go 注释 下一篇Go面经 | 成都Go面试这么卷?卷王..

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目