Go 的用户代码初始化流程与 C/C++ 类似,都是先初始化全局变量,然后才调应用的 main 函数,但这个顺序限定在 package 内部,即在 package 内部初始化顺序为:
每个 package 只会初始化一次,package 之间的初始化顺序由 import 顺序决定,如下图所示:
简单验证 import 顺序关系影响变量初始化顺序的代码如下:
1 2 3 4 5 6 7 8 9 10 ~$ tree . ├── go.mod ├── main.go ├── pkga │ └── a.go ├── pkgb │ └── b.go └── pkgc └── c.go
pkga/a.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package pkgaimport ( "fmt" ) const ConstA = 'A' var VarA = initVarA()func initVarA () rune { fmt.Println("-- VarA init()" ) return ConstA } func init () { fmt.Println("-- pkga init()" ) fmt.Printf("VarA = %c\n" , VarA) }
pkgb/b.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package pkgbimport ( "fmt" "init/pkga" ) const ConstB = 'B' var VarB = initVarB()func initVarB () rune { fmt.Println("-- VarB init()" ) fmt.Printf("VarA = %c\n" , pkga.VarA) return ConstB } func init () { fmt.Println("-- pkgb init()" ) fmt.Printf("VarB = %c\n" , VarB) }
pkgc/c.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package pkgcimport ( "fmt" "init/pkga" ) const ConstC = 'C' var VarC = initVarC()func initVarC () rune { fmt.Println("-- VarC init()" ) fmt.Printf("VarA = %c\n" , pkga.VarA) return ConstC } func init () { fmt.Println("-- pkgc init()" ) fmt.Printf("VarC = %c\n" , VarC) }
main.go,其中 import 顺序为 pkgb -> pkgc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package mainimport ( "fmt" "init/pkgb" "init/pkgc" ) const ConstM = 'M' var VarM = initVarM()func initVarM () rune { fmt.Println("-- VarM init()" ) fmt.Printf("ConstM = %c\n" , ConstM) return pkgc.VarC } func main () { fmt.Println("-- main():" ) fmt.Printf("VarM = %c\n" , VarM) fmt.Printf("VarB = %c\n" , pkgb.VarB) fmt.Printf("VarC = %c\n" , pkgc.VarC) }
输出如下,初始化顺序为 pkga -> pkgb -> pkgc -> package main -> func main:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 ~$ go run main.go -- VarA init() -- pkga init() VarA = A -- VarB init() VarA = A -- pkgb init() VarB = B -- VarC init() VarA = A -- pkgc init() VarC = C -- VarM init() ConstM = M -- main(): VarM = C VarB = B VarC = C
main.go,其中 import 顺序为 pkgc -> pkgb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package mainimport ( "fmt" "init/pkgc" "init/pkgb" ) const ConstM = 'M' var VarM = initVarM()func initVarM () rune { fmt.Println("-- VarM init()" ) fmt.Printf("ConstM = %c\n" , ConstM) return pkgc.VarC } func main () { fmt.Println("-- main():" ) fmt.Printf("VarM = %c\n" , VarM) fmt.Printf("VarB = %c\n" , pkgb.VarB) fmt.Printf("VarC = %c\n" , pkgc.VarC) }
输出如下,初始化顺序为 pkga -> pkgc -> pkgb -> package main -> func main:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 ~$ go run main.go -- VarA init() -- pkga init() VarA = A -- VarC init() VarA = A -- pkgc init() VarC = C -- VarB init() VarA = A -- pkgb init() VarB = B -- VarM init() ConstM = M -- main(): VarM = C VarB = B VarC = C
注意如下的代码是非法的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package mainimport ( "fmt" ) const ConstA1 = '1' const ConstA2 = '2' var VarA1 = initVarA1()var VarA2 = initVarA2()func initVarA1 () rune { fmt.Println("-- VarA1 init()" ) VarA2 = ConstA2 return VarA2 } func initVarA2 () rune { fmt.Println("-- VarA2 init()" ) VarA1 = ConstA1 return VarA1 } func init () { fmt.Println("-- pkga init()" ) fmt.Printf("VarA1 = %c\n" , VarA1) fmt.Printf("VarA2 = %c\n" , VarA2) } func main () {}
会报如下的错误,这也是 Go 不支持循环 import 的一个原因:
1 2 3 4 5 6 7 8 ~$ go run main.go ./main.go:10:5: initialization loop: /home/runsisi/working/test/loop/main.go:10:5 VarA1 refers to /home/runsisi/working/test/loop/main.go:13:6 initVarA1 refers to /home/runsisi/working/test/loop/main.go:11:5 VarA2 refers to /home/runsisi/working/test/loop/main.go:19:6 initVarA2 refers to /home/runsisi/working/test/loop/main.go:10:5 VarA1
参考资料 init functions in Go
https://medium.com/golangspec/init-functions-in-go-eac191b3860a
When is the init() function run?
https://stackoverflow.com/questions/24790175/when-is-the-init-function-run
Go, without package scoped variables
https://dave.cheney.net/2017/06/11/go-without-package-scoped-variables