首发于Go语言101

Go 1.13版本的改动和Go SDK 1.13的各项增强

从Go 1.0发布到当前的1.12版本,Go语言本身的语法改动非常少。这使得一本五年前出版的Go语言编程书在当前看来仍然不是很过时。

去年,Go官方设计团队宣布了Go 2计划。Go 2只是一个象征性名词,它并不代表着Go 2.x版本,而是表示Go语言将在后续版本中迎来较大变化。Go 1.13将是一个开始此大变化的起步版本。Go SDK 1.13测试版昨天已经发布了,本文余下的部分将列出一些(可能)在Go 1.13中将引入的变化。

语言上的改动

移位操作中的右操作数将允许为有符号整数

截至目前,移位操作(<<>>)中的右操作数必须为无符号类型确定整数或者可以表示成uint值的类型不确定值。从Go 1.13开始,移位操作中的右操作数将可以是任何整数类型(包括有符号类型)的类型确定值或者可以表示成任何整数类型的不确定值。但是如果一个右操作数是一个常量,则它仍旧必须为非负。

比如,下面这行在Go 1.12-版本中编译不过,但是从Go 1.13开始,将编译没问题。

var _ = 1 << int(5)

注意,当一个非常量负整数被用作移位操作中的右操作数时,此操作将造成一个恐慌,比如下面这段代码:

var x = -2
var _ = 1 << x // panic!

更多的数值字面表示形式

截至Go 1.12,Go只支持三种整数字面表示形式:十六进制形式、八进制形式和十进制形式。比如,下面是十进制数15的三种字面表示形式:

0xF // 十六进制表示(必须使用0x或者0X开头)
017 // 八进制表示(必须使用0开头)
15  // 十进制表示(必须不能用0开头)

Go 1.13将引入二进制表示形式(千呼万唤始出来)。二进制形式必须使用0b或者0B开头,后面跟随的数字只能为01。一个示例:

0b11001 // 十进制的25的二进制字面表示
0B111   // 十进制的7的二进制字面表示

从Go 1.13开始,八进制数除了目前的使用0开头,将也可以使用0o或者0O开头。比如,下面三个八进制表示是等价的。

017  // 15的八进制表示
0o17 // 15的八进制表示
0O17 // 15的八进制表示

另外,浮点数的字面表示形式也得到了扩充。可以在内存中精确表示的浮点数将可以用十六进制文字表示来表示。

  • 和整数的十六进制文字表示一样,浮点数的十六进制文字表示也必须使用0x或者0X开头。
  • 和整数的十六进制文字表示不同的是,字母p或者P可以出现在浮点数的十六进制文字表示中,其后跟随着一个幂指数(以2为基)。
  • 另外要注意,以10为基的eE不能出现在浮点数的十六进制文字表示中。

一些浮点数的十六进制文字表示例子:

0x1p-2     // == 0.25
0x2.p10    // == 2048.0
0x1.Fp+0   // == 1.9375
0X.8p-0    // == 0.5
0X1FFFP-16 // == 0.1249847412109375

而下面这几个均不是合法的浮点数的十六进制文字表示。

0x.p1    // 浮点数的十六进制文字表示必须包含至少一个数字
1p-2     // p指数形式只能出现在浮点数的十六进制文字表示中
0x1.5e-2 // e和E不能出现在浮点数的十六进制文字表示中

下面这个表示是合法的,但是它不是浮点数的十六进制文字表示。事实上,它是一个减法算术表达式。其中的e为是十进制中的14,0x15e为一个整数十六进制文字表示,-2并不是此整数十六进制文字表示的一部分。

0x15e-2 // == 0x15e - 2 (整数相减表达式)

数值字面表示形式的最后一点改动是:下划线_可以出现在数值字面表示形式中用做分段符以增强可读性。但是要注意,在一个数值字面表示中,一个下划线_不能出现在此字面表示的首尾,并且其两侧的字符必须为(相应进制的)数字字符或者进制表示头。

一些合法和不合法使用下划线的例子:

// 合法的使用下划线的例子
6_9          // == 69
0_33_77_22   // == 0337722
0x_Bad_Face  // == 0xBadFace
0X_1F_FFP-16 // == 0X1FFFP-16

// 非法的使用下划线的例子
_69        // 下划线不能出现在首尾
69_        // 下划线不能出现在首尾
6__9       // 下划线不能相连
0_xBadFace // x不是一个合法的八进制数字
1_.5       // .不是一个合法的十进制数字
1._5       // .不是一个合法的十进制数字

另外,虚部中i前的数字放宽到可以是各种进制的数字字面值。

标准库的改动

reflect标准库包的变化

reflect标准库包中将为Value类型添加一个IsZero方法,用来判断一个值是否为零值。

errors标准库包的功能的增强

除了上述语法变化,Go 1.13中很可能将对errors标准库包的功能(当前只包含一个原型为func New(text string) error的函数)进行增强。由于篇幅原因,本文将不详述这些增强。这里只列出errors标准库包将要添加的三个函数:

  • 一个原型为func Is(err, target error) bool的包级函数用来检查一个error链是否存在和一个目标error匹配的error。默认匹配算法是使用==来检查两个error是否相等,但是一个自定义error类型可以实现一个原型为Is(target error) bool的方法来压制默认认匹配算法。
  • 一个原型为func As(err error, target interface{}) bool的函数用来检查一个error链是否存在和一个目标error的类型匹配的error。传递给target的实参的具体值必须为一个基类型巍峨实现了内置error接口的非零指针值。如果检查结果为匹配,则target的实参的具体值引用的error值将被设为error链中的相应匹配的error值。
  • Unwrap(err error) error函数用来取出被包裹的error(即error链中的下一个error)。

更多标准库的改动请阅读官方release node.

SDK的增强

Go SDK 1.13之前,很多可以安全地开辟在栈上的内存因为编译器中的逃逸分析器不够智能的原因而被开辟在了堆上。这在一定程度上影响了程序执行效率。从Go SDK 1.13开始,标准编译器将采用一个新的逃逸分析器,从而将避免很了多不必要的在堆上开辟内存的情况。

Go SDK 1.13之前,所有的defer延迟调用都是记录在堆上的,这严重影响了defer延迟调用的执行效率。从Go SDK 1.13开始,满足某些条件的某些defer延迟调用(标准库中93%的延迟调用满足此条件)将被记录在栈上而不是堆上,从而提高了defer延迟调用的执行效率。

官方Go runtime 1.13将对sync.Pool中的对象回收时机策略做出调整。在1.12版本及以前的版本中,在每轮垃圾回收过程中,每个sync.Pool实例中的所有缓存对象都将被无条件回收掉。从1.13版本开始,如果一个sync.Pool实例在上一轮垃圾回收过程结束之后仍然被使用过,则其中的缓存对象将不会被回收掉。此举对于使用sync.Pool来提升效率的程序来说,将大大减少周期性的因为缓存被清除而造成的瞬时效率下降。

从Go SDK 1.13开始,编译输出的二进制可执行文件中将包含所用Go编译器的版本和此执行文件所依赖的各种第三方库包的版本号。我们可以使用命令go version binaryfile来查看一个二进制可执行文件是使用哪个版本的编译器编译的,或者使用命令go version -m binaryfile来查看包含在一个二进制可执行文件中的各个第三方依赖库包的版本号。

经过Go 1.11和Go 1.12近一年时间的磨合,Go modules版本依赖管理特性将从Go SDK 1.13开始大规模推荐使用。GOPATH环境变量的地位将减弱甚至丧失。GOBIN环境变量的地位将提升,因为go install命令仍需要一个路径来存储生成的二进制可执行文件。另外,伴随Go modules而生的GOPROXY环境变量的设置格式得到了增强。我们可以使用下面的格式来指定多个代理:

GOPROXY=proxy1,proxy2,proxy3

go命令在需要下载库包的时候将逐个试用设置中的各个代理,直到发现一个可用的为止。特别地,direct表示直连。一个设置例子:

GOPROXY=direct,https://proxy.golang.org,https://myproxy.mysite:8888

GOPROXY环境变量可以帮助我们下载墙外的第三方库包。当然,通过设置https_proxy环境变量设也可以达到此目的。但是一个公司通过在内部架设一个自己的goproxy服务器来缓存第三方库包,库包下载速度可能会更快。

为了防止出现node.js社区中大量的在使用npm时造成的不经意间引入木马库包的情况,Go官方推出了Go checksum database(sumdb):sum.golang.orggo命令将在必要的时候连接此服务来检查下载的第三方依赖包的哈希是否和sumdb的记录相匹配。有些遗憾,和proxy.golang.org类似,sum.golang.org也被墙了。但是我们同样可以设置https_proxy代理或者架设自己的sumdb服务器来解决这个问题。GOSUMDB环境变量用来设置第三方sum database服务器地址,其默认值为sum.golang.org。我们可以将其值设为off来关闭哈希检查,go命令的选项-insecure发挥同样的作用。当然,为了安全起见,一般情况下最好不要关闭哈希检查。

在设置了GOPROXY环境变量的情况下,我们可以设置GONOPROXY环境变量来设置不需要通过goproxy服务器来下载的库包。在设置了GOSUMDB环境变量的情况下,我们可以设置GONOSUMDB环境变量来设置不需要哈希检查的库包。

通过以上介绍,我们可以看到,go命令需要的环境变量增加了不少。为了管理这些环境变量,go env子命令添加了一个选项-w,用来设置全局go环境变量。比如,在Linux系统上,命令go env -w GOBIN=$HOME/bin用来设置GOBIN环境变量。


更多关于Go语言的细节、技巧和常识,请访问《Go语言101》项目或者《Go语言101》官网,或者关注本专栏公众号(Go 101):


发布于 2019-07-21 14:39