一如既往,go1.17继续满足go1.x兼容性原则,以前的源代码无需任何修改,就可以在go1.17上正确的编译和运行。
语言层面的改动
首先是slice可以直接转换成array pointer(数组指针)。直接来看一个例子:
package main
import (
"fmt"
)
func main() {
s := []int{6, 9, 4, 10, 15}
fmt.Printf("len(s) = %d, cap(s) = %d\n", len(s), cap(s))
a := (*[5]int)(s)
fmt.Printf("len(a) = %d, cap(a) = %d\n", len(a), cap(a))
fmt.Printf("type of s: %T, type of a: %T\n", s, a)
for i := 0; i < 5; i++ {
fmt.Printf("&s[%d] == &a[%d]: %t\n", i, i, &s[i] == &a[i])
}
}
复制
上面的例子是将一个类型为[]int的slice直接转换成类型为*[5]int的array pointer。输出结果为:
len(s) = 5, cap(s) = 5
len(a) = 5, cap(a) = 5
type of s: []int, type of a: *[5]int
&s[0] == &a[0]: true
&s[1] == &a[1]: true
&s[2] == &a[2]: true
&s[3] == &a[3]: true
&s[4] == &a[4]: true
复制
这里要注意一点,array的长度必须小于等于slice的长度,也就是len(s),否则会产生runtime panic。如果将上面的例子的第10行中的5改成6,则运行code时会产生如下panic:
panic: runtime error: cannot convert slice with length 5 to pointer to array with length 6
复制
语言层面的另一个改动就是在unsafe包中增加了两个函数:Add和Slice。
// The function Add adds len to ptr and returns the updated pointer
// Pointer(uintptr(ptr) + uintptr(len)).
// The len argument must be of integer type or an untyped constant.
// A constant len argument must be representable by a value of type int;
// if it is an untyped constant it is given type int.
// The rules for valid uses of Pointer still apply.
func Add(ptr Pointer, len IntegerType) Pointer
// The function Slice returns a slice whose underlying array starts at ptr
// and whose length and capacity are len.
// Slice(ptr, len) is equivalent to
//
// (*[len]ArbitraryType)(unsafe.Pointer(ptr))[:]
//
// except that, as a special case, if ptr is nil and len is zero,
// Slice returns nil.
//
// The len argument must be of integer type or an untyped constant.
// A constant len argument must be non-negative and representable by a value of type int;
// if it is an untyped constant it is given type int.
// At run time, if len is negative, or if ptr is nil and len is not zero,
// a run-time panic occurs.
func Slice(ptr *ArbitraryType, len IntegerType) []ArbitraryType
复制
函数自带的注释已经说的很清楚了,这两个函数只是为了开发者更容易写出符合unsafe安全规则的代码。Add(ptr, len)等价于:
Pointer(uintptr(ptr) + uintptr(len))
复制
Slice(ptr, len)等价于:
(*[len]ArbitraryType)(unsafe.Pointer(ptr))[:]
复制
移植兼容性
对于Darwin,Go 1.17要求 >= macOS 10.13。
对于Windows,Go1.17增加了对64位ARM架构的支持,也支持cgo。
windows/arm64
复制
对于OpenBSD,64位的MIPS架构开始支持cgo。另外,以前在Go 1.16中,只有64位的x86和ARM架构的系统调用是通过libc来完成;在Go1.17中,32位的x86和ARM架构的系统调用也是通过libc来完成的了。
工具集
这方面的变化其实挺多的,但是关键是Go module以及go:build这两个变化。
Go module
Go1.17中一个显著的改变就是支持修剪的模块依赖图(pruned module graph)。
在以前的版本中,一个模块的依赖由其直接依赖与所有的间接依赖组成,也就是一个全集的依赖图;每个go.mod文件中只包含直接依赖。运行go命令的时候会解析当前go.mod以及每个依赖的go.mod文件,哪怕其实并不需要。
在Go1.17中,go.mod文件只会包含该模块真正需要的依赖,也即是imports的包。但是会将其直接依赖和所有真正需要的间接依赖都包含在go.mod文件中。这样,会导致go.mod文件增大很多,所以对于间接依赖,会包含在一个单独的require块中。
将一个现有的go.mod转换成Go1.17,可以运行下面的命令:
go mod tidy -go=1.17
复制
例如,针对etcd 3.5运行上面的命令,对go.mod的改变如下:
关于这部分的具体细节,参考:
https://tip.golang.org/ref/mod#graph-pruning
复制
//go:build
熟悉Golang交叉编译的同学应该对"// +build "这种语法并不陌生。其缺点是不直观,空格表示"或",逗号表示"与",不好记忆,也容易出错。例如,下面这个例子表示 (linux AND 386) OR darwin。
// +build linux,386 darwin
复制
在Go1.17中,开始推荐使用"//go:build"这种语法形式。上面的例子按照新语法就是:
// go:build linux && 386 || darwin
复制
当使用gofmt格式化源代码的时候,如果有"// +build"这种语法,gofmt会在其上面自动加上对应的"//go:build"语法形式。例如:
//go:build mips || mipsle
// +build mips mipsle
复制
关于"//go:build"的详细细节,参考:
https://go.googlesource.com/proposal/+/master/design/draft-gobuild.md
复制
编译器(Compiler)
以前传递函数参数以及返回值是通过栈,Go117的一个主要的变化就是支持通过寄存器传递函数参数以及返回值。直接的好处就是性能的提高(5%)以及二进制文件大小的减小(2%)。这个改变对程序员来说,一般是没有影响的;但是如果使用汇编或者unsafe包等方式来访问函数参数或者函数本身的地址,那可能会受到影响,毕竟传参的方式变化了,导致函数本身的地址偏移自然会随之变化。
另外一个变化是支持内联包含闭包的函数。这样就导致同一个函数可能会内联到不同的地方,从而其包含的闭包的地址也不同。例如下面的函数Demo包含闭包,当它被内联到不同的地方之后,其包含的内部函数的地址就会不同。当采用unsafe.Pointer的方式来比较其地址的时候,就可能不再相等。
func Demo() func() int {
a := 3
return func() int {
return a * 10
}
}
复制
Core Library
Core library的小变化挺多的,这里只提其中两个容易产生错误的地方。第一个改动就是net/url和net/http包不再支持分号(;)作为参数的分隔符。例如下面这个请求地址,在Go1.17之前的版本中会解析成三个参数;而在Go1.17中只会解析成一个参数c=3。
example?a=1;b=2&c=3
复制
另外一个值得注意的改动就是x509ignoreCN这个临时变量被删除了。这就意味着证书中如果还只有CN而没有SAN的时候,在Go1.17中就无法正常工作了。具体可以参考我以前对1.15以及1.16的新特性解读。
GODEBUG=x509ignoreCN=0
复制
总结
对于大多数开发人员来说,重点需要关注的主要是以下几点:
1. slice可以转换成array pointer;
2. 对于pruned module graph的支持;
3. 用"//go:build"取代"// +build";
4. x509ignoreCN被彻底删除。
--END--