标签 泛型 下的文章

Go 1.17中值得关注的几个变化

本文永久链接 – https://tonybai.com/2021/08/17/some-changes-in-go-1-17

Go核心开发团队在去年GopherCon大会上给Go泛型的定调是在2022年2月份的Go 1.18版本中发布,那可是自Go诞生以来语法规范变动最大的一次,这让包括笔者在内的全世界的Gopher们都满怀期待。

不过别忘了,在Go 1.18这个“网红版本”发布前,还有一个“实力派”版本Go 1.17呢!美国当地时间2021年8月16日,Go 1.17版本在经过两个RC版本之后正式发布!并且值得庆幸的是Go 1.17版本并没有过多受到Go 1.18版本这个“网红”的影响,Go 1.17默默地加入和优化了着实不少的特性。在这一篇文章中,我们就来看看Go 1.17版本中有哪些值得关注的变化。

1. 语言特性变化

Go属于那种极简的语言,从诞生到现在语言自身特性变化很小,不会像其他主流语言那样走“你有的我也要有”的特性融合路线。因此新语言特性对于Gopher来说属于“稀缺品”,属于“供不应求”那类事物^_^。这也直接导致了每次Go新版本发布,我们都要首先看看语言特性是否有变更,每个新加入语言的特性都值得我们去投入更多关注,去深入研究。Go 1.17在语言特性层面做了两方面的小改动,下面我们来看看。

第一个是对语言类型转换规则的扩展,允许从切片到数组指针的转换,下面的代码在Go 1.17版本中是可以正常编译和运行的:

// github.com/bigwhite/experiments/tree/master/go1.17-examples/lang/slice2arrayptr/main.go
func slice2arrayptr() {
    var b = []int{11, 12, 13}
    var p = (*[3]int)(b)
    p[1] = p[1] + 10
    fmt.Printf("%v\n", b) // [11 22 13]
}

Go通过运行时对这类切片到数组指针的转换代码做检查,如果发现越界行为,就会通过运行时panic予以处理。Go运行时实施检查的一条原则就是“转换后的数组长度不能大于原切片的长度”,注意这里是切片的长度(len),而不是切片的容量(cap)。

第二个变动则是unsafe包增加了两个函数:Add与Slice。使用这两个函数可以让开发人员更容易地写出符合unsafe包使用的安全规则的代码。这两个函数原型如下:

// $GOROOT/src/unsafe.go
func Add(ptr Pointer, len IntegerType) Pointe
func Slice(ptr *ArbitraryType, len IntegerType) []ArbitraryType

unsafe.Add允许更安全的指针运算,而unsafe.Slice允许更安全地将底层存储的指针转换为切片。

2. go module的变化

Go 1.11版本引入go module以来,每个Go大版本发布时,go module都会有不少的积极变化,这是Go核心团队与社区就go module深入互动的结果。

Go 1.17中go module同样有几处显著变化,其中最最重要的一个变化就是pruned module graph(修剪的module依赖图)。Go 1.17之前的版本某个module的依赖图由该module的直接依赖以及所有间接依赖组成,无论某个间接依赖是否真正为原module的构建做出贡献,这样go命令在解决依赖时会读取每个依赖的go.mod,包括那些没有被真正使用到的module,这样形成的module依赖图被称为完整module依赖图(complete module graph)

Go 1.17不再使用“完整module依赖图”,而是引入了pruned module graph(修剪的module依赖图)。修剪的module依赖图就是在完整module依赖图的基础上将那些“占着茅坑不拉屎”、对构建完全没有“贡献”的间接依赖module修剪后的依赖图。使用修剪后的module依赖图进行构建将有助于避免下载或阅读那些不必要的go.mod文件,这样Go命令可以不去获取那些不相关的依赖关系,从而在日常开发中节省时间。

但module依赖图修剪也带来了一个副作用,那就是go.mod文件size的变大。因为Go 1.17版本后,每次go mod tidy(当go.mod中的go版本为1.17时),go命令都会对main module的依赖做一次深度扫描(deepening scan),并将main module的所有直接和间接依赖都记录在go.mod中(之前说的版本只记录直接依赖)。考虑到内容较多,go 1.17将直接依赖和间接依赖分别放在两个不同的require块儿中。

3. 编译器与运行时的变化

Go 1.17增加了对Windows上64位ARM架构的支持,让开发者可以在更多设备上原生运行Go。但这个版本编译器最大的变化是在amd64架构下率先实现了从基于堆栈的调用惯例到基于寄存器的调用惯例切换

并且,切换到基于寄存器的调用惯例后,一组有代表性的Go包和程序的基准测试显示,Go程序的运行性能提高了约5%,二进制文件大小典型减少约2%。也就是说你的Go源码使用Go 1.17版本重新编译一下就能获得大约5%的性能提升,真希望这样的优化越多越好!对更多平台的基于寄存器调用惯例的支持将在未来的版本中出现。

除了改为基于寄存器的调用惯例之外,Go 1.17编译器还支持包含闭包的函数的内联(inline)了!这样一来,一个带有闭包的函数可能会在函数被内联的每个地方产生一个不同的闭包代码指针,因此,
Go函数的值不能直接比较

Go编译器还在Go 1.17中引入了//go:build形式的构建约束指示符,以替代原先易错的// +build形式。

4. 其他变化

  • 保留龙芯架构GOARCH值

在Go 1.17版本中,Go编译器保留了中国龙芯cpu架构的GOARCH值 – loong64。关于龙心GOARCH值选用loong64还是loongarch64还有过一段激烈的争论,最终大多数都赞同的loong64取得了最后的胜利。

  • Go test变化

Go test引入-shuffle的洗牌标志位,用以控制单元测试或benchmark的执行顺序。

另外T和B两个类型分别都增加了Setenv方法用于在test和benchmark执行期间设置环境变量。

  • time包增加Time对象的GoString形式输出

我们使用%#v输出一个Time对象实例时,Go 1.17之前的版本输出内容如下面:

Go 1.16.5输出:

time.Time{wall:0xc03f08c0d06c9ed0, ext:83078, loc:(*time.Location)(0x11620e0)}

Go 1.17增加了GoString方法,该方法在Time对象以%#v格式输出时被自动调用,其输出结果如下:

time.Date(2021, time.August, 17, 20, 29, 42, 58245000, time.Local)

5. 小结

除上述变化之外,Go的其他标准库随着新版本的发布也都会有大量的小改动,但每个开发人员对标准库的关注点差别很大,因此,在这个系列中不会详细做说明了,大家还是参考Go 1.17的发布说明文档各取所需吧^_^。

与传统的“Go新版本值得关注的几个变化”系列有所不同,本期内容较为简单和概括,因为更多内容,我将在后续的Go 1.17新特性详解系列中针对上述值得关注的新特性做进一步说明。详解系列已经写好,不过首发在了本人运营的星球“Gopher部落”上了,如果你迫切想深入了解这些新特性,可以加入星球阅读。

本文所涉及的源码可以在这里 – https://github.com/bigwhite/experiments/tree/master/go1.17-examples/


“Gopher部落”知识星球正式转正(从试运营星球变成了正式星球)!“gopher部落”旨在打造一个精品Go学习和进阶社群!高品质首发Go技术文章,“三天”首发阅读权,每年两期Go语言发展现状分析,每天提前1小时阅读到新鲜的Gopher日报,网课、技术专栏、图书内容前瞻,六小时内必答保证等满足你关于Go语言生态的所有需求!部落目前虽小,但持续力很强。在2021年上半年,部落将策划两个专题系列分享,并且是部落独享哦:

  • Go技术书籍的书摘和读书体会系列
  • Go与eBPF系列

欢迎大家加入!

Go技术专栏“改善Go语⾔编程质量的50个有效实践”正在慕课网火热热销中!本专栏主要满足广大gopher关于Go语言进阶的需求,围绕如何写出地道且高质量Go代码给出50条有效实践建议,上线后收到一致好评!欢迎大家订
阅!

img{512x368}

我的网课“Kubernetes实战:高可用集群搭建、配置、运维与应用”在慕课网热卖中,欢迎小伙伴们订阅学习!

img{512x368}

我爱发短信:企业级短信平台定制开发专家 https://tonybai.com/。smspush : 可部署在企业内部的定制化短信平台,三网覆盖,不惧大并发接入,可定制扩展; 短信内容你来定,不再受约束, 接口丰富,支持长短信,签名可选。2020年4月8日,中国三大电信运营商联合发布《5G消息白皮书》,51短信平台也会全新升级到“51商用消息平台”,全面支持5G RCS消息。

著名云主机服务厂商DigitalOcean发布最新的主机计划,入门级Droplet配置升级为:1 core CPU、1G内存、25G高速SSD,价格5$/月。有使用DigitalOcean需求的朋友,可以打开这个链接地址:https://m.do.co/c/bff6eed92687 开启你的DO主机之路。

Gopher Daily(Gopher每日新闻)归档仓库 – https://github.com/bigwhite/gopherdaily

我的联系方式:

  • 微博:https://weibo.com/bigwhite20xx
  • 微信公众号:iamtonybai
  • 博客:tonybai.com
  • github: https://github.com/bigwhite
  • “Gopher部落”知识星球:https://public.zsxq.com/groups/51284458844544

微信赞赏:
img{512x368}

商务合作方式:撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。

Go泛型语法又出“幺蛾子”:引入type set概念和移除type list中的type关键字

本文永久链接 – https://tonybai.com/2021/04/07/go-generics-use-type-sets-to-remove-type-keyword

近日,Go泛型语法负责人之一的Ian Lance Taylor发布了一个issue,说明go团队想引入新的type set概念,并去除原Go泛型方案中置于interface定义中的type list中的type关键字。

对于Go泛型来龙去脉不是很了解的童鞋,可以先去看看我看看我之前的文章:《能力越大,责任越大” – Go语言之父详解将于Go 1.18发布的Go泛型》。在那篇文章的结尾,Go设计团队对自己的Go泛型设计方案中的几个方面给出了自己的满意度评价,其中唯一让团队感觉还不是很完美的就是“Type lists in interfaces”:

1. 何为Type lists in interfaces

我们先来说说何为Type lists in interfaces!当前Go泛型方案使用interface类型用于表达对类型参数(type parameters)的约束(constraints),比如:

type MyC1 interface {
    M1()
}

func F1[T MyC1](t T) {

}

在上述代码中,我们使用interface MyC1作为类型参数(type parameters)的约束,对于F1函数而言,所有满足MyC1接口的类型都可以作为其类型参数的实参传入:

type MyT1 string
func(t1 *MyT1) M1() {}

var t1 = new(MyT1)
F1(t1)

*MyT1实现了MyC1接口,于是我们可以将其实例(t1)传给F1。Go泛型的自动类型推导会将T的实参置为*MyT1。

完整程序如下:

// https://go2goplay.golang.org/p/WPCvmwkxcEL
package main

import (
    "fmt"
)

type MyC1 interface {
    M1()
}

func F1[T MyC1](t T) {
    fmt.Printf("%T\n", t)
}

type MyT1 string

func (t1 *MyT1) M1() {

}

func main() {
    var t1 = new(MyT1)
    F1(t1) // *main.MyT1
}

对于自定义类型,通过实现接口的方法集合即可满足接口,对于类型参数可以是原生类型的情况,我们无法通过这种方式实现,于是Go团队将type list加入到interface接口中,仅用作泛型类型参数的约束检查

type MyC2 interface {
    type int, int32, int64
}

func F2[T MyC2](t T) {
    fmt.Printf("%T\n", t)
}

func main() {
    var t2 string
    F2(t2) // string
}

而MyMC2中的:

    type int, int32, int64

就是所谓的”type list”。

如果一个interface定义中既有method也有type list,那么要满足这个interface类型,则作为类型参数实参的类型既必须在type list中(或其underlying type在type list中),又必须实现接口类型的所有方法:

// https://go2goplay.golang.org/p/rE8mGH0lHWm
package main

import (
    "fmt"
)

type MyC3 interface {
    M3()
    type int, string, float64
}

func F3[T MyC3](t T) {
    fmt.Printf("%T\n", t)
}

type MyT3 string

func (t3 MyT3) M3() {

}

func main() {
    t3 := MyT3("hello")
    F3(t3) // main.MyT3
}

细心的童鞋会发现:拥有type list的interface仅能用于做为类型参数的约束,而不能像普通interface类型那样使用:

// https://go2goplay.golang.org/p/mJoEYrceBSL
package main

type MyC3 interface {
    M3()
    type int, string, float64
}

func main() {
    var i3 MyC3 // type checking failed for main
                    // prog.go2:9:9: interface contains type constraints (int, string, float64)
    _ = i3
}

这种gap(缝隙)始终让Go核心团队的开发人员感到“不爽”,那么能否将两者融合在一起呢?即放开对包含type list的interface类型仅能做constraint的限制,让其和普通interface一样使用。这次引入的type set应该是解决这个问题的一个前提。但在这个新proposal中,核心团队还没有将这个问题作为重点,只能算作是为以后留个作业吧。

2. 引入type set概念

Ian Lance Taylor发布的这个issue主要就是想引入type set概念,并用新语法等价替代原泛型proposal中的type list,新语法去除了原type list中的type关键字

于是go团队试图这样来做:

// 当前的type list
type SignedInteger interface {
    type int, int8, int16, int32, int64
}

// type set理念下的新语法
type SignedInteger interface {
    ~int | ~int8 | ~int16 | ~int32 | ~int64
}

我们看到新语法中去掉了原先type list中的type关键字,类型间的间隔也由逗号改为了管道符|。按该proposal的原意,管道符(在布尔代数中也表示或)更接近于type list的原意,即可以是int,或int8或….。如果仅仅是变成了如下改进的语法:

type SignedInteger interface {
    int | int8 | int16 | int32 | int64
}

估计大家也没多大意见。但是偏偏引入了“~”这个前缀。~int与int有什么区别呢?要搞清楚区别就要先来看看Ian新引入的type set概念了。

什么是type set(类型集合)?Ian给出了此概念的定义:

  • 每个类型都有一个type set。
  • 非接口类型的类型的type set中仅包含其自身。比如非接口类型T,它的type set中唯一的元素就是它自身:{T};
  • 对于一个普通的、没有type list的普通接口类型来说,它的type set是一个无限集合。所有实现了该接口类型所有方法的类型都是该集合的一个元素,另外由于该接口类型本身也声明了其所有方法,因此接口类型自身也是其Type set的一员。
  • 空接口类型interface{}的type set中则是囊括了所有可能的类型;
  • 这样一来我们来试试用type set概念重新陈述一下一个类型T实现一个接口类型I:即当类型T是接口类型I的type set的一员时,T便实现了接口I;
  • 对于使用嵌入接口类型组合而成的接口类型,其type set就是其所有的嵌入的接口类型的type set的交集。proposal中的举例:type O2 interface{ E1; E2 } ,则02这个接口类型的type set是E1和E2两个接口类型的type set的交集。
  • 一个拥有一个method的接口类型,比如:
type MyInterface1 interface {
    MyMethod()
}

可以看成嵌入一个仅包含MyMethod的接口类型的接口类型:

type MyInterface interface {
    MyMethod()
}
type MyInterface1 interface {
    MyInterface
}
  • 因此,一个带有自身Method的嵌入其他接口类型的接口类型,比如:
type 03 interface {
    E1
    E2
    MyMethod03()
}

它的type set可以看成E1、E2和E3(type E3 interface { MyMethod03})的type set的交集。

3. 替换type list的新语法方案

我们再回到前面提到的新语法方案:

// type set 新语法
type SignedInteger interface {
    ~int | ~int8 | ~int16 | ~int32 | ~int64
}

Go开发团队给那些用于作为约束或被嵌入到作为约束的接口类型中的接口类型的定义做了重新描述,称这类接口类型的定义中可以嵌入一些额外的结构,被称为interface elements,其组成如下图:

  • 图中MyInterface是一个仅用于约束或嵌入到作为约束的接口类型中的类型;
  • MyInterface除了拥有自己的方法列表(M1、M2)外,还可以嵌入额外的结构:interface elements,就是T1|T2|~T3|T4…|Tn那一行,这一行即替代了原先方案中的type list;
  • interface elements这一行有三个值得关注的事情:
    • T1、T2、T4、Tn这些仅代表type set仅为自身的类型;
    • ~T3的type set 为所有underlying type为T3的类型,~T3被称为approximation elements;
    • 管道符将这些类型连接在一起,共同构成一个union element,该union element的type set为所有这些类型的type set的并集。

好了现在一切都建立在type set这个概念上。那么当上述接口类型作为类型参数的约束时,要想满足该约束,可以作为类型参数的实参,那么传入的类型应该在作为约束的接口类型的type set中。

有了前面关于type set以及接口嵌入的type set的铺垫,作为约束的接口类型的理解就容易多了。无论是单纯的接口类型还是使用嵌入其他接口组合而成的接口类型,亦或是既包括嵌入也拥有自己的method list的接口类型。

4. 问题

Ian的issue一发出就得到了社区的重点关注,并引来的激烈的讨论,但从头看到尾,似乎大家都有些“跑题”,关于这个proposal的真正疑问在于approximation elements身上:

  • 是否有必要单独拿出approximation elements这个概念

我们回顾一下当前泛型语法作为约束的接口定义所使用的type list语法,看看当前的type list语法中各个类型是否是仅代表自身?

// https://go2goplay.golang.org/p/5VbaSCQ8-Dq
package main

import (
    "fmt"
)

type S1 struct {
    Name string
    Age  int
}

type S2 S1

type MyC4 interface {
    type struct {
        Name string
        Age  int
    }, int
}

func F4[T MyC4](t T) {
    fmt.Printf("%T\n", t)
}

type MyInt int

func main() {
    var t1 = S1{"tony", 17}
    F4(t1) // main.S1
    var t2 = S2{"tony", 17}
    F4(t2) // main.S2
    var n MyInt = 3
    F4(n) // main.MyInt
}

我们看到作为约束的接口类型MyC4的type list中有两个类型:一个匿名struct和int。之后我们分别使用S1、S2和MyInt作为类型参数的实参,居然都通过了!也就是说当前的type list中的类型按照type set的概念解释,都属于approximation element,只要是underlying type在type list中,那么就可以作为类型参数的实参,通过约束检查。

那就是说:

我们是否可以只将:

type I1 interface {
    type int, string, float64
    ... ...
}

换成:

type I1 interface {
    int | string | float64
    ... ...
}

而无需~这个符号呢?

  • 如果~符号是必要的,可否不用~符号?

Go语言中没有使用~运算符,但这个符号在其他主流语言,比如C中是位运算符,而且代表的“非”这个运算符。因此将其用在类型T前面,打眼一看,以为其含义是“不是类型T的类型”。而新proposal则将其用于表示approximation element。这让很多gopher提出异议,希望换一个符号,比如T+等。但目前尚无定论。

5. 小结

能力有限,以上一些对该proposal的理解可能有误,欢迎交流指正。

type set并没有改变什么,只是完成了对interface与实现interface的重新解释。 但是对于后续将interface element用于普通interface类型定义可能有重大的意义。当前的带有interface element的interface类型仅能用于作为泛型类型参数的约束,这与普通interface之间的gap早晚要“填上”,不过这已经不是这个proposal要解决的事情。

从泛型提出到如今,我已经感到泛型的引入极大增加了复杂性 ,即便没有滥用泛型,没有耍奇技淫巧,泛型的引入也让go复杂性陡增。就像这个proposal,认真阅读并理解还是需要花费不少时间和精力的。


“Gopher部落”知识星球正式转正(从试运营星球变成了正式星球)!“gopher部落”旨在打造一个精品Go学习和进阶社群!高品质首发Go技术文章,“三天”首发阅读权,每年两期Go语言发展现状分析,>每天提前1小时阅读到新鲜的Gopher日报,网课、技术专栏、图书内容前瞻,六小时内必答保证等满足你关于Go语言生态的所有需>求!部落目前虽小,但持续力很强。在2021年上半年,部落将策划两个专题系列分享,并且是部落独享哦:

  • Go技术书籍的书摘和读书体会系列
  • Go与eBPF系列

欢迎大家加入!

Go技术专栏“改善Go语⾔编程质量的50个有效实践”正在慕课网火热热销中!本专栏主要满足>广大gopher关于Go语言进阶的需求,围绕如何写出地道且高质量Go代码给出50条有效实践建议,上线后收到一致好评!欢迎大家订
阅!

img{512x368}

我的网课“Kubernetes实战:高可用集群搭建、配置、运维与应用”在慕课网热卖>中,欢迎小伙伴们订阅学习!

img{512x368}

我爱发短信:企业级短信平台定制开发专家 https://tonybai.com/。smspush : 可部署在企业内部的定制化短信平台,三网覆盖,不惧大并发接入,可定制扩展; 短信内容你来定,不再受约束, 接口丰富,支持长短信,签名可选。2020年4月8日,中国三大电信运营商联合发布《5G消息白皮书》,51短信平台也会全新升级到“51商用消息平台”,全面支持5G RCS消息。

著名云主机服务厂商DigitalOcean发布最新的主机计划,入门级Droplet配置升级为:1 core CPU、1G内存、25G高速SSD,价格5$/月。有使用DigitalOcean需求的朋友,可以打开这个链接地址:https://m.do.co/c/bff6eed92687 开启你的DO主机之路。

Gopher Daily(Gopher每日新闻)归档仓库 – https://github.com/bigwhite/gopherdaily

我的联系方式:

  • 微博:https://weibo.com/bigwhite20xx
  • 微信公众号:iamtonybai
  • 博客:tonybai.com
  • github: https://github.com/bigwhite
  • “Gopher部落”知识星球:https://public.zsxq.com/groups/51284458844544

微信赞赏:
img{512x368}

商务合作方式:撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。

如发现本站页面被黑,比如:挂载广告、挖矿等恶意代码,请朋友们及时联系我。十分感谢! Go语言第一课 Go语言进阶课 AI原生开发工作流实战 Go语言精进之路1 Go语言精进之路2 Go语言第一课 Go语言编程指南
商务合作请联系bigwhite.cn AT aliyun.com

欢迎使用邮件订阅我的博客

输入邮箱订阅本站,只要有新文章发布,就会第一时间发送邮件通知你哦!

这里是 Tony Bai的个人Blog,欢迎访问、订阅和留言! 订阅Feed请点击上面图片

如果您觉得这里的文章对您有帮助,请扫描上方二维码进行捐赠 ,加油后的Tony Bai将会为您呈现更多精彩的文章,谢谢!

如果您希望通过微信捐赠,请用微信客户端扫描下方赞赏码:

如果您希望通过比特币或以太币捐赠,可以扫描下方二维码:

比特币:

以太币:

如果您喜欢通过微信浏览本站内容,可以扫描下方二维码,订阅本站官方微信订阅号“iamtonybai”;点击二维码,可直达本人官方微博主页^_^:
本站Powered by Digital Ocean VPS。
选择Digital Ocean VPS主机,即可获得10美元现金充值,可 免费使用两个月哟! 著名主机提供商Linode 10$优惠码:linode10,在 这里注册即可免费获 得。阿里云推荐码: 1WFZ0V立享9折!


View Tony Bai's profile on LinkedIn
DigitalOcean Referral Badge

文章

评论

  • 正在加载...

分类

标签

归档



View My Stats