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--