Go 1.18.1 Beta 尝鲜

2022-10-26 15:23:55 浏览数 (4)

Go 1.18.1 Beta 尝鲜

昨天,go 终于发布了 1.18 的 beta 版本, 带来了大家期待已久的泛型,抓紧时间康康能不能赶上热乎的。

下载地址

根据社区昨天发的 Go 1.18 Beta 1 is available, with generics 这次版本更新主要带来的新功能有:

  • 泛型
  • 模糊测试( fuzzing-based tests)
  • workspace mode
  • arm64 和 PPC64 也增加了基于寄存器的调用规约
  • 增加了一个 go version -m 可以记录构建细节
  • 其他,参见 draft release notes for Go 1.18

泛型

在没有泛型之前,假设我们需要求两个数的和,根据运算数的类型,可能需要写很多个函数,如:

代码语言:javascript复制
package main

func SumInt64(a, b int64) int64 {return a   b}

func SumFloat64(a, b int64) float64 {return a   b}

有了泛型之后就可以这样写了:

代码语言:javascript复制
package main

func Sum2[V int | int64 | float64 | int32 | float32](a, b V) V {
    return a   b
}

上面的代码在 [] 中声明了一个泛型 V 它支持 int, int64, int32, float32, float64 五种类型,函数有两个 V 类型的参数 a 和 b 此外函数返回值也是 V 类型

我还是挺好奇如果传入的参数不是这五种会报什么错:

代码语言:javascript复制
//go:build go1.18
//  build go1.18
package main

import "fmt"

func Sum2[V int64 | float64 | int32 | float32](a, b V) V {
    return a   b
}

func main() {
    fmt.Println(Sum2[int](1, 2))
}

编译时报错:

代码语言:javascript复制
# go1.18.1-beta/1.18-beta/generic/generic
.main.go:20:21: int does not implement int64|float64|int32|float32

注意,在调用 Sum2 时,我们使用 [] 显示地制定了 Vint 类型,在编译器可以推断类型时,这个是可以省略的,也就是可以写作

代码语言:javascript复制
func main() {
    fmt.Println(Sum2(1, 2))
}

但这并不是一直有用的,比如你要调用一个没有参数的泛型函数时,如:

代码语言:javascript复制
func PI[V int | float64]() V {
   var v V
   v = 10.0
   return v
}

func main() {
    // fmt.Println(PI())  // .main.go:28:16: cannot infer V 
    fmt.Println(PI[float64]()) // 10
}

此外,都知道 go map 的 key 要求是可比较的类型,因此,go 新增了一个关键字 comparable 表明泛型是一个可比较类型, 当泛型参数作为 map 的 key 时,它必须是可比较的。

代码语言:javascript复制
//go:build go1.18
//  build go1.18
package main

import "fmt"

func Sum[K comparable, V int64 | float64](m map[K]V) V {
    var sum V
    for k, v := range m {
        sum  = v
        fmt.Println(k)
    }
    return sum
}

func main() {
    fmt.Println(Sum(map[int64]float64{1: 2.3, 2: 3.3}))
}

是不是觉得每次 int | int64 | float64 | int32 | float32 写太麻烦了,确实,为此 go1.18 提供了泛型接口,你可以像定义接口一样定义一个泛型类型,就像:

代码语言:javascript复制
type Number interface {
    int | int8 | int16 | int32 | int64 | float32 | float64
}

在这之后,你就可以使用 Number 来代替这一长串了

模糊测试

模糊测试 (fuzz testing, fuzzing)是一种软件测试技术。其核心思想是将自动或半自动生成的随机数据输入到一个程序中,并监视程序异常,如崩溃,断言(assertion)失败,以发现可能的程序错误,比如内存泄漏。模糊测试常常用于检测软件或计算机系统的安全漏洞。 —— wikipedia 模糊测试

可以看看官网的这个例子

代码语言:javascript复制
func FuzzHex(f *testing.F) {
    for _, seed := range [][]byte{{}, {0}, {9}, {0xa}, {0xf}, {1, 2, 3, 4}} {
        f.Add(seed)
    }
    f.Fuzz(func(t *testing.T, in []byte) {
        enc := hex.EncodeToString(in)
        out, err := hex.DecodeString(enc)
        if err != nil {
            t.Fatalf("%v: decode: %v", in, err)
        }
        if !bytes.Equal(in, out) {
            t.Fatalf("%v: not equal after round trip: %v", in, out)
        }
    })
}

运行 go test -fuzz=Fuzz 即可进行模糊测试,用法和普通测试差不多,如果有需要请移步官方文档

workspace mode

这是非常爽的一个功能,想想这样一个场景,为了方便测试,你需要要改某一个功能(有时可能只是一个数值),但这个功能是一个单独的模块,通过 mod 引入,所以你下载了这个包,并用 replace 将其替换成了本地的路径,就像:

代码语言:javascript复制
module go1.18.1-beta

go 1.18

replace (
    github.com/json-iterator/go => /usr/bin/go/json-iterator/go
)

然后你就可以开心的改本地的模块了,但问题在于你每次提交代码时都需要回滚改过的 go.mod 否则大家就都用不了了……

workspace mode 就是解决了这样的问题,它引入了一个 go.work 文件,你可以在项目目录下执行 go work init . 来生成它,需要注意的是 workspace mode 只能用在 goMod 中,所以目录下必须有 go.mod 才能生成 go.work, 刚生成的文件内容类似:

代码语言:javascript复制
go 1.18

use ./.

go.work 中我们可以使用 replace:

代码语言:javascript复制
go 1.18

use ./.

replace (
    github.com/json-iterator/go => /usr/bin/go/json-iterator/go
)

go 会优先选择 go.work 中的模块,这样你把 go.work 加入 .gitignore 就可以舒服地改代码了

再看看上面的文件,事实上,在提案上,只有三个元素:

The go.work file has three directives: the go directive, the directory directive, and the replace directive.

在 beta 版中, directory 被改成了 use, 这三个元素的作用是:

  • go: 指明一个 go 版本
  • use: 将包含go.mod文件的目录的绝对或相对路径作为参数。路径的语法与replace指令中的目录替换相同。路径必须是包含go.mod文件的模块目录。go.work文件必须至少包含一个use指令。
代码语言:javascript复制
use (
    ./tools // golang.org/x/tools
    ./mod   // golang.org/x/mod
)
  • replace: 与 go mod 中的一样

可以简单的理解为 go.work 声明了一个工作目录,这个目录下的成员由 use 声明,在工作目录下执行构建时,会优先使用工作目录下的组件。

看这个例子

代码语言:javascript复制
cd ~/project/go-beta/work
mkdir a b c
cd a
go mod init github.com/520MianXiangDuiXiang520/a
cd ../b
go mod init github.com/520MianXiangDuiXiang520/b
cd ../c
go mod init c
cd ..
go mod init work

当 work 引用 a b 时,由于这两个项目在 github 上不存在,所以之前只能使用 replace:

代码语言:javascript复制
module work

go 1.18

replace (
    github.com/520MianXiangDuiXiang520/a => ./a
    github.com/520MianXiangDuiXiang520/b => ./b
    c => ./c
)

require (
    github.com/520MianXiangDuiXiang520/a v0.0.0-00010101000000-000000000000
    github.com/520MianXiangDuiXiang520/b v0.0.0-00010101000000-000000000000
    c v0.0.0-00010101000000-000000000000
)

使用 workspace mode 后:

代码语言:javascript复制
cd ~/project/go-beta/work
go work init . ./a ./b ./c

go mod 中可以只写:

代码语言:javascript复制
module work

go 1.18

因为他们在同一个工作目录下

基于寄存器的调用规约

在 go 1.17 时就针对 X86-64 的处理器增加了这个,据说函数调用性能能提斯 20%,现在拓展到了 arm64 和 PPC64 但我没有这种处理器的电脑,不过可以对比一下旧版的函数调用方式:

代码语言:javascript复制
package main

func demo(a int64, b int32, c int16, d int8) (int64, int32, int16, int8) {
    a  = 111
    b  = 222
    c  = 333
    d  = 89
    return a, b, c, d
}

func main() {
    demo(0, 0, 0, 0)
}

在 go 1.14 的环境下,将上面的代码编译并输出汇编代码如下:

代码语言:javascript复制
go build -gcflags="-l -S" main.go
代码语言:javascript复制
"".demo STEXT nosplit size=55 args=0x20 locals=0x0
        0x0000 00000 (E:go汇编1.go:3)  TEXT    "".demo(SB), NOSPLIT|ABIInternal, $0-32
        0x0000 00000 (E:go汇编1.go:3)  PCDATA  $0, $-2
        0x0000 00000 (E:go汇编1.go:3)  PCDATA  $1, $-2
        0x0000 00000 (E:go汇编1.go:3)  FUNCDATA        $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
        0x0000 00000 (E:go汇编1.go:3)  FUNCDATA        $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
        0x0000 00000 (E:go汇编1.go:3)  FUNCDATA        $2, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
        0x0000 00000 (E:go汇编1.go:4)  PCDATA  $0, $0
        0x0000 00000 (E:go汇编1.go:4)  PCDATA  $1, $0
        0x0000 00000 (E:go汇编1.go:4)  MOVQ    "".a 8(SP), AX
        0x0005 00005 (E:go汇编1.go:4)  ADDQ    $111, AX
        0x0009 00009 (E:go汇编1.go:8)  MOVQ    AX, "".~r4 24(SP)
        0x000e 00014 (E:go汇编1.go:5)  MOVL    "".b 16(SP), AX
        0x0012 00018 (E:桌面文件笔记Noteg ogo汇编1.go:5)  ADDL    $222, AX
        0x0017 00023 (E:go汇编1.go:8)  MOVL    AX, "".~r5 32(SP)
        0x001b 00027 (E:go汇编1.go:6)  MOVWLZX "".c 20(SP), AX
        0x0020 00032 (E:go汇编1.go:6)  ADDL    $333, AX
        0x0025 00037 (E:go汇编1.go:8)  MOVW    AX, "".~r6 36(SP)
        0x002a 00042 (E:go汇编1.go:7)  MOVBLZX "".d 22(SP), AX
        0x002f 00047 (E:go汇编1.go:7)  ADDL    $89, AX
        0x0032 00050 (E:go汇编1.go:8)  MOVB    AL, "".~r7 38(SP)
        0x0036 00054 (E:go汇编1.go:8)  RET
        0x0000 48 8b 44 24 08 48 83 c0 6f 48 89 44 24 18 8b 44  H.D$.H..oH.D$..D
        0x0010 24 10 05 de 00 00 00 89 44 24 20 0f b7 44 24 14  $.......D$ ..D$.
        0x0020 05 4d 01 00 00 66 89 44 24 24 0f b6 44 24 16 83  .M...f.D$$..D$..
        0x0030 c0 59 88 44 24 26 c3                             .Y.D$&.

1.18 编译结果如下:

代码语言:javascript复制
# command-line-arguments
"".demo STEXT nosplit size=20 args=0x10 locals=0x0 funcid=0x0 align=0x0
        0x0000 00000 (E:add.go:3)      TEXT    "".demo(SB), NOSPLIT|ABIInternal, $0-16
        0x0000 00000 (E:add.go:3)      FUNCDATA        $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
        0x0000 00000 (E:add.go:3)      FUNCDATA        $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
        0x0000 00000 (E:add.go:3)      FUNCDATA        $5, "".demo.arginfo1(SB)
        0x0000 00000 (E:add.go:3)      FUNCDATA        $6, "".demo.argliveinfo(SB)
        0x0000 00000 (E:add.go:3)      PCDATA  $3, $1
        0x0000 00000 (E:add.go:4)      ADDQ    $111, AX
        0x0004 00004 (E:add.go:5)      ADDL    $222, BX
        0x000a 00010 (E:add.go:6)      ADDL    $333, CX
        0x0010 00016 (E:add.go:7)      ADDL    $89, DI
        0x0013 00019 (E:add.go:8)      RET
        0x0000 48 83 c0 6f 81 c3 de 00 00 00 81 c1 4d 01 00 00  H..o........M...
        0x0010 83 c7 59 c3   

结果一目了然吧,两个都开了编译优化 -N 1.14 用的完全是栈, 1.18 用了四个寄存器: AX BX CX DI,那最多会用多少个寄存器呢?

代码语言:javascript复制
# command-line-arguments
"".demo STEXT nosplit size=66 args=0x48 locals=0x0 funcid=0x0 align=0x0
        0x0000 00000 (E:add.go:3)      TEXT    "".demo(SB), NOSPLIT|ABIInternal, $0-72
        0x0000 00000 (E:add.go:3)      FUNCDATA        $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
        0x0000 00000 (E:add.go:3)      FUNCDATA        $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
        0x0000 00000 (E:add.go:3)      FUNCDATA        $5, "".demo.arginfo1(SB)
        0x0000 00000 (E:add.go:3)      FUNCDATA        $6, "".demo.argliveinfo(SB)
        0x0000 00000 (E:add.go:3)      PCDATA  $3, $1
        0x0000 00000 (E:add.go:14)     MOVQ    "".j 8(SP), DX
        0x0005 00005 (E:add.go:14)     ADDQ    $787, DX
        0x000c 00012 (E:add.go:16)     MOVQ    DX, "".~r9 16(SP)
        0x0011 00017 (E:add.go:5)      ADDQ    $111, AX
        0x0015 00021 (E:add.go:6)      ADDL    $222, BX
        0x001b 00027 (E:add.go:7)      ADDL    $333, CX
        0x0021 00033 (E:add.go:8)      ADDL    $89, DI
        0x0024 00036 (E:add.go:9)      ADDQ    $99, SI
        0x0028 00040 (E:add.go:10)     ADDQ    $88, R8
        0x002c 00044 (E:add.go:11)     ADDQ    $999, R9
        0x0033 00051 (E:add.go:12)     ADDQ    $898, R10
        0x003a 00058 (E:add.go:13)     ADDQ    $989, R11
        0x0041 00065 (E:add.go:16)     RET
        0x0000 48 8b 54 24 08 48 81 c2 13 03 00 00 48 89 54 24  H.T$.H......H.T$
        0x0010 10 48 83 c0 6f 81 c3 de 00 00 00 81 c1 4d 01 00  .H..o........M..
        0x0020 00 83 c7 59 48 83 c6 63 49 83 c0 58 49 81 c1 e7  ...YH..cI..XI...
        0x0030 03 00 00 49 81 c2 82 03 00 00 49 81 c3 dd 03 00  ...I......I.....
        0x0040 00 c3     

答案是 9 个 超出的部分会按顺序放在栈上

go version

这个指令最基本的用法是查看 go 版本

代码语言:javascript复制
E:1.18-betaas>go version
go version go1.18beta1 windows/amd64

但其实它还可以看 go 编译产物的构建版本信息,这次增加了一个 -m 参数:

代码语言:javascript复制
E:1.18-betaas>go version -m add.exe
add.exe: go1.18beta1
        path    command-line-arguments
        build   -compiler=gc
        build   -gcflags=-l -S
        build   CGO_ENABLED=1
        build   CGO_CFLAGS=
        build   CGO_CPPFLAGS=
        build   CGO_CXXFLAGS=
        build   CGO_LDFLAGS=
        build   GOARCH=amd64
        build   GOOS=windows
        build   GOAMD64=v1

参考

Go 1.18 Beta 1 is available, with generics

Tutorial: Getting started with generics

pkg.go.dev#Fuzzing

Proposal: Multi-Module Workspaces in cmd/go

0 人点赞