语法特性

切片 -> 数组指针

数组切片化,转换后,数组将成为切片的底层数组

1
2
3
4
a := [3]int{11, 12, 13}
sl := a[:]
sl[1] += 10
fmt.Printf("%T, %v\n", sl, sl) // []int, [11 22 13]

在 Go 1.17 之前,不支持将切片转换为数组类型,仅可以通过 unsafe 包以不安全的方式实现转换
unsafe 包的安全性没有得到编译器运行时保证,尽量不要使用

1
2
3
4
sl := []int{11, 12, 13}
var p = (*[3]int)(unsafe.Pointer(&sl[0]))
p[1] += 10
fmt.Printf("%v\n", sl) // [11 22 13]

从 Go 1.17 开始,支持从切片转换为数组类型指针

1
2
3
4
sl := []int{11, 12, 13}
var p = (*[3]int)(sl)
p[1] += 10
fmt.Printf("%v\n", sl) // [11 22 13]

Go 会通过运行时而非编译器去对转换代码进行检查,如果发现越界行为,触发运行时 panic
检查原则:转换后的数组长度不能大于原切片的长度(len) - 为了防止数组越界

1
2
3
4
sl := []int{11, 12, 13}

var p1 = (*[4]int)(sl) // panic: runtime error: cannot convert slice with length 3 to pointer to array with length 4
var p2 = (*[3]int)(sl[:1]) // panic: runtime error: cannot convert slice with length 1 to pointer to array with length 3
1
2
3
4
5
6
7
sl := []int{11, 12, 13}

fmt.Printf("%v\n", *(*[0]int)(sl)) // []
fmt.Printf("%v\n", *(*[1]int)(sl)) // [11]
fmt.Printf("%v\n", *(*[2]int)(sl)) // [11 12]
fmt.Printf("%v\n", *(*[3]int)(sl)) // [11 12 13]
fmt.Printf("%v\n", *(*[4]int)(sl)) // panic: runtime error: cannot convert slice with length 3 to pointer to array with length 4

nil 切片或者 cap 为 0 的空切片,都可以被转换为 *[0]int

1
2
3
4
5
var s1 []int                      // nil slice
fmt.Printf("%T\n", (*[0]int)(s1)) // *[0]int

var s2 = []int{} // empty slice
fmt.Printf("%T\n", (*[0]int)(s2)) // *[0]int

Go Module

  1. complete module graph
    • 在 Go 1.17 之前,某个 Module 的依赖图由该 Module 的直接依赖以及所有间接依赖组成
    • 无论某个间接依赖是否真正为原 Module 的构建做出贡献,Go 命令在解决依赖时都会读取每个依赖go.mod
  2. pruned module graph
    • 从 Go 1.17 开始,在 complete module graph 的基础上,裁剪那些对构建完全没有贡献间接依赖
    • 使用 pruned module graph 进行构建,Go 命令不会去获取不相关的依赖关系,节省时间

image-20231127225220973

Go 1.17 之前建立的 Go Module
在 go.mod 经过 go mod tidy 后, require 仅保留的都是 main module 的直接依赖(没有间接依赖)

1
2
3
4
5
6
7
8
9
10
11
12
module example.com/lazy

go 1.15

require example.com/a v0.1.0 // 核心

replace (
example.com/a v0.1.0 => ./a
example.com/b v0.1.0 => ./b
example.com/c v0.1.0 => ./c1
example.com/c v0.2.0 => ./c2
)
1
2
3
4
$ go mod graph
example.com/lazy example.com/[email protected]
example.com/[email protected] example.com/[email protected]
example.com/[email protected] example.com/[email protected]

移除 example.com/c v0.1.0 => ./c1,构建失败

1
2
3
4
5
6
7
$ go build
go: example.com/[email protected] requires
example.com/[email protected]: missing go.sum entry; to add it:
go mod download example.com/c
go: example.com/[email protected] requires
example.com/[email protected]: missing go.sum entry; to add it:
go mod download example.com/c

修改 go.mod,将 go 1.15 修改为 go 1.17,执行 go mod tidy 重新构建 go.mod,新增记录了间接依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
module example.com/lazy

go 1.17

require example.com/a v0.1.0

require example.com/b v0.1.0 // indirect

replace (
example.com/a v0.1.0 => ./a
example.com/b v0.1.0 => ./b
example.com/c v0.1.0 => ./c1
example.com/c v0.2.0 => ./c2
)

移除 example.com/c v0.1.0 => ./c1,构建成功

  1. module c 并没有为 main module 的构建提供代码级贡献,Go 命令将其从 main module 的依赖图中裁剪
  2. 副作用:go.modsize 变大
    • 从 Go 1.17 开始,每次调用 go mod tidy,都会对 main module 的依赖做一次深度扫描
    • 并将 main module 的所有直接和间接依赖都记录在 go.mod,并存放在不同的 require 块中

go install golang.org/x/exp/cmd/txtar@latest