init函数在包被导入时自动执行且仅一次,早于main函数;不接受参数、不返回值,不可显式调用;按文件字典序执行,依赖包init先于依赖方执行。

init 函数什么时候会被调用
init 函数在包被导入时自动执行,且只执行一次,早于 main 函数。它不接受参数、不返回值,也不能被显式调用——你写 init() 会直接编译报错:undefined: init。
常见错误现象:想用 init 做“按需初始化”,比如等某个配置加载完再跑逻辑,结果发现它早就执行完了,配置变量还是零值。
- 多个
init函数在同一个包里,按源文件字典序执行(不是声明顺序) - 依赖链中,被依赖包的
init总是先于依赖方执行(比如import "net/http"会触发net和http包各自的init) - 如果包没被任何地方引用(包括没被
main导入),它的init根本不会触发
init 里能干啥,不能干啥
能做的:注册驱动(database/sql)、预热全局变量、检查必要环境(如 os.Getenv 是否存在)、设置日志前缀。不能做的:启动 goroutine 后不等它结束就退出(main 返回后程序终止,goroutine 被强制杀死);阻塞等待外部信号;调用尚未初始化的其他包变量(除非明确知道初始化顺序)。
典型翻车场景:在 init 里读配置文件并赋值给全局 config 结构体,但文件路径来自 flag.String ——flag.Parse() 在 main 里才调,init 阶段 flag 还没解析,路径是空字符串,读取失败静默。
立即学习“go语言免费学习笔记(深入)”;
- 推荐只做无副作用、不依赖运行时输入的操作
- 需要外部输入的初始化,应挪到
main开头或封装成显式Init()函数 - 避免在
init中 panic,否则整个程序启动失败,堆栈不友好(只显示runtime.main)
多个 init 函数的执行顺序和调试方法
Go 编译器按文件名排序依次执行每个文件里的 init,比如 a.go 的 init 一定在 b.go 之前。但同一文件内多个 init 函数?不允许——编译报错:multiple <code>init functions in file。
调试难点在于:没有断点、不能 print(除非用 log 且确保输出不被缓冲),出问题时只能靠加日志+看执行顺序推测。一个简单验证方式:
package main
import "log"
func init() {
log.Println("a.go init")
}
另建 b.go 写同理日志,运行就能看出顺序。
- 别指望靠注释或函数名控制
init执行时机 - 跨包依赖的初始化顺序由 import 路径决定,不是 import 语句顺序
- 用
go tool compile -S xxx.go可看到编译器生成的初始化函数调用链,但一般没必要
init 和 var 初始化、const 的区别
const 是编译期常量,无执行成本;var 声明带初始值的,会在 init 阶段之前完成(即“包级变量初始化”阶段),但复杂表达式(含函数调用)仍算进 init 流程。比如 var x = time.Now(),这个 time.Now() 实际上是在某个隐式 init 里调的。
容易忽略的细节:如果两个包互相 import(循环导入),编译直接失败;但更隐蔽的是“间接循环”——A → B → C → A,这种 Go 也能检测出来,报错:import cycle not allowed。而 init 就卡在这条链上,根本跑不到。
- 尽量把可延迟的初始化逻辑从
init挪走,尤其是涉及 I/O、网络、命令行参数的 - 全局变量初始化表达式越简单越好,避免隐藏的
init行为 - 测试时注意:
go test会导入被测包,触发其init,哪怕测试函数没显式用到


















