结论:初始化顺序严格定义:依赖的导入包先初始化(深度优先)→ 包内先初始化包级变量(按依赖关系排序,无依赖则按声明顺序)→ 再按文件被编译器呈现的顺序执行各 init() 函数(同文件可多个 init)→ 最后 main.main。整个初始化在单 goroutine 中串行完成。
展开:要点:1)init 无参数无返回值,不能被显式调用,import 即触发(包括匿名导入 _ "pkg"——这是注册驱动的惯用法,如 database/sql 的驱动注册);2)跨包顺序由导入图决定,与 import 列表书写顺序无关;同一包内跨文件顺序以文件名字典序(go 工具链约定),不应依赖它写逻辑;3)init 里 panic 整个进程崩掉且堆栈不友好,因此 init 不该做可能失败的事(连数据库、读远程配置)——这类工作放进显式的 New()/Start() 让调用方处理错误。易错点:测试时 init 同样执行(测试二进制含被测包的 init),慢 init 拖慢全部测试;init 之间通过包级变量隐式耦合是循环依赖和"玄学初始化 bug"的温床,社区共识是 init 只做纯本地的注册/查表类工作。
// 推荐:init 只做无副作用注册
func init() {
sql.Register("mydriver", &Driver{})
}
// 不推荐:init 里连接数据库
追问方向:包级变量初始化表达式依赖图的构建规则、如何避免 import cycle(接口下沉、依赖注入)。