Go 1.16于今天凌晨(北京时间)发布了,基本还是半年一个release的节奏。总的来说,新特性很少,基本都是一些改进。


Go 1.16同样遵守了Go 1.x兼容性承诺。也就是以前的程序依然不用任何改动,就可以正确地在Go1.16上编译并运行。


语言层面改动

语言层面的改动,只指程序员在写代码的时候能感知到的变化。这里要明确说明一下,Go 1.16在语言层面没有任何改动。


这里简单回顾一下Go 1.15和Go 1.14在语言层面的改动。Go 1.15在语言层面同样也没有任何改变。Go 1.14主要增加对钻石型(菱形)接口嵌套结构的支持。具体请参考:



Ports(移植兼容性)

Darwin and iOS

Go 1.16增加对64位ARM架构+macOS的支持(也称为Apple Silicon),跨平台build的时候如下设置GOOS与GOARCH:

GOOS=darwin GOARCH=arm64


对于iOS的支持,以前是darwin/arm64,现在更名为ios/arm64,所以跨平台build的时候如下设置GOOS与GOARCH:

GOOS=ios GOARCH=arm64
# 如果linux平台,则如下设置
GOOS=android GOARCH=arm64


Go 1.16也增加了对ios/amd64的支持,主要是为了在AMD64 macOS上运行iOS模拟器。


Go 1.16是最后一个支持mac 10.12 (Sierra)的版本。在Go 1.17中将要求macOS 10.13 (High Sierra)或者更新的版本。


NetBSD

在NetBSD上增加了对64位ARM的支持。

GOOS=netbsd GOARCH=arm64


OpenBSD

现在支持MIPS64。不过目前还不支持cgo。

GOOS=openbsd GOARCH=mips64


值得注意的一点是,在OpenBSD上,对于64位x86和64位ARM是通过libc进行系统调用,而不是直接通过SYSCALL/SVC指令了。这主要是为了与将来的OpenBSD版本保持向前兼容。在OpenBSD 6.9之后,要求通过libc进行系统调用。


386

Go 1.16不再支持x87模式(GO386=387)编译了。对于非SSE2处理器的支持可以通过软浮点模式(GO386=softfloat)来实现。


RISC-V

对于linux/risv64,Go 1.16现在支持cgo以及-buildmode=pie。Go 1.16也包含了一些针对RISC-V的性能优化。


工具集

Go command

不管当前工作目录或者父目录是否包含go.mod文件,都会默认打开(enable) Module模式。也就是说,环境变量GO111MODULE默认是设置成on;如果要继续保持以前版本的行为,则需要手动将其设置成auto。


在以前的版本中,运行任何go命令都会自动更新go.mod和go.sum。在Go 1.16中,“go build”和“go test”不会再自动更新go.mod和go.sum了。如果它们发现需要更新这两个文件,就会报错。


但是"go get"和“go tidy"仍会自动更新go.mod和go.sum。对于"go get"会自动更新go.mod和go.sum,社区进行了大量的讨论,普遍认为不应该自动更新这两个文件。具体可参考:

https://github.com/golang/go/issues/27643
https://github.com/golang/go/issues/30515
https://github.com/golang/go/issues/40276



为了解决这个问题,应该使用"go install";也就是当需要build并install一个包的时候,应该使用"go install",而不是"go get"。

https://go-review.googlesource.com/c/proposal/+/243077


"go install"可以指定特定的版本,例如:

go install example.com/cmd@v1.0.0


对于"go get",正确的使用姿势应该是带上选项“-d”,只用来更新依赖项,而不build包。在将来的版本中,“-d”将会始终被enable。


另外,可以在go.mod文件中使用retract指令来废弃一个特定的版本。以前的行为是使用比废弃版本高的下一个版本。


内嵌文件

在Go 1.16中增加了一个新的指令//go:embed,来支持内嵌静态文件。关于这一点,我以前的一篇文章中有介绍,请参考:



go test

当运行"go test"时,如果一个测试函数中调用了os.Exit(0),那么测试函数被认为是失败了。但是在TestMain中调用os.Exit(0),仍然被认为是通过了测试。


go get

在"go get"中使用 "-insecure"已经是deprecated了,在将来的版本中会被删除。如果还想继续使用不安全的协议(比如HTTP),那就使用环境变量GOINSECURE。如果想绕过module sum的校验,那就使用环境变量GOPRIVATE或者GONOSUMDB。


环境变量GOVCS

GO 1.16中增加了一个新的环境变量GOVCS,用来限制使用哪个版本控制工具来下载源代码。


Cgo

cgo工具不再将C语言里的位域字段翻译成Go语言里结构体中的字段。


Vet

Vet新增了几条告警。首先,如果在测试函数创建的goroutine中调用Fatal/Fatalf/FailNow/Skip/Skipf/SkipNow等方法,则vet会产生warning。调用这些方法会导致测试函数创建的gorouting停止执行,但是不会影响测试函数自身。推荐的做法是使用t.Error产生错误,并用其它方法退出goroutine,例如return。


当使用汇编指令操作BP寄存器(frame pointer),但没有保存并恢复该寄存器时,Vet也会产生warning。


当传递非指针参数或者nil给asn1.Unmarchal时,vet也会产生warning。


Runtime

首先,引入了新的包runtime/metrics,来取代runtime/ReadMemStats和debug.GCStats。


其次,设置GODEBU为inittrace=1,那么runtime会将每一个package中的init函数的执行时间和内存分配等信息输出到stderr。


在Linux平台上,runtime现在默认是立即释放(MADV_DONTNEED)不需要的内存,而不是拖延到OS遇到memory pressure(MADV_FREE)的时候才释放。所以,对于进程的内存统计信息会更加准确。对于目前在使用该选项(如下)的系统,在Go 1.16中已经不需要设置该选项了,因为默认就是enable了,

GODEBUG=madvdontneed=1


Compiler

编译器现在可以内联(inline)不带label的for循环、方法值以及type switch的函数了。也包含了其它方面的内联优化。


Linker

在GO 1.16中,linker做了很大的改进。首先是减少了资源的使用,包括时间和内存方面。也提高了代码的健壮性和可维护性。


在Go 1.15中,对linker的性能优化主要集中在ELF-based OS以及amd64上;而在Go 1.16中,对linker的性能优化在涵盖了所有支持的OS/ARCH组合。而且比Go 1.15的链接速度更快,需要的内存更少。


Core library

内嵌文件

前面已经提到过,在编译期间可以通过新的指令//go:embed来内嵌静态文件。新增的包embed,就是用来访问这些内嵌的文件。


文件系统

引入了新包io/fs,其中定义了接口fs.FS,用来处理只读的文件树(read-only trees of files)。embed.FS、zip.Reader以及os.DirFS均实现了fs.FS。


函数http.FS将一个fs.FS实例转化成一个http.FileSystem实例。html/template与text/template中的函数ParseFS从一个fs.FS实例中读取模版。


对于实现了fs.FS的测试代码,新包testing/fstest提供了一个函数TestFS用来检查具体的实现。另外,也提供了一个简单的基于内存的文件系统的实现MapFS。


Deprecation of io/ioutil

包io/ioutil已经被deprecated了。io/ioutil中的"函数/方法/类型"应该用包io以及os中的相应对象替代,具体见下表:

io/ioutil中的"对象"(deprecated)
io或os中的"对象"(new)
Discard
io.Discard
NopCloser
io.NopCloser
ReadAll
io.ReadAll
ReadDir
os.ReadDir
ReadFile
os.ReadFile
TempDir
os.MkdirTemp
TempFile
os.CreateTemp
WriteFile
os.WriteFile


Minor changes to the libary

Libray中小改动还是挺多的,这里只是选取几个简要说一下。


archive/zip中的方法Reader.Open实现了fs.FS接口。


包crypto/x509的改动。选项"GODEBUG=x509ignoreCN=0"将在Go 1.17中被删除。如果对于Go 1.15中的变动了解的话,就知道这个改动意味着什么了。在Go 1.14以及以前的版本中,当X.509证书中没有Subject Alternative Name时,就会使用CommonName作为hostname。而在Go 1.15中,CommonName默认是被deprecated的。如果还想继续使用CommonName,则可以给环境变量GODEBUG增加"x509ignoreCN=0"。在Go 1.16中可以继续使用该选项,但是在Go 1.17中将会被删除。另外,值得注意的是,ParseCertificate与CreateCertificate开始强制对DNSNames、EmailAddress以及URIs字段实行字符串编码限制,这些字段只能包含ASCII范围内的字符。


包encoding/asn1的改动。当传递给Unmarshal与UnmarshalWithParams的参数不是指针或者是nil时,会返回一个error,而不是panic了。


对Unicode的支持从12.0.0升级到了13.0.0,增加了5930个新的字符。


总结

总的来说,Go 1.16中的新特性比较少,大部分都是一些改进和优化,在本文开头也提到了。


对于绝大多数开发人员来说,最值得关注新特性应该还是对内嵌文件的支持。一个相关的关注点就是io/ioutil被deprecated,要用包io与os中相应的对象来替换。


另外,如果单纯只是想安装一个包,应该使用"go install",而不是"go get"。


最后,要提一下“GODEBUG=x509ignoreCN=0”,这个选项在Go 1.17中会被删除。因为去年下半年在用户的生产环境中,遇到过这个问题,用户的证书里面没有用SAN,而是用的CN,而我们一个团队盲目升级到了Go 1.15,结果就出问题了,临时的解决方案就是增加这个runtime选项。


--END--