init函数在包加载时自动执行一次且早于main:跨包按依赖拓扑序(被依赖包先执行),同包内按文件名字典序,同一文件内按源码自上而下顺序;不可显式调用,不接受参数、无返回值。

init函数的执行时机和顺序规则
Go 语言中 init 函数不是普通函数,不能被显式调用,它只在包加载时自动执行一次,且严格按依赖顺序触发:先执行被依赖包的 init,再执行当前包的 init。多个 init 函数在同一文件中会按源码出现顺序执行;跨文件则按编译器遍历文件的顺序(通常按字母序),但不应依赖该顺序。
常见错误是误以为 init 能“延迟初始化”或“按需触发”——它实际发生在 main 函数之前,哪怕该包只是被间接导入,也会执行。
- 如果包 A 导入包 B,B 的
init一定在 A 的init之前完成 - 循环导入会导致编译失败,
init不会绕过这个限制 - 测试文件(
_test.go)中的init只在运行go test时执行,不影响正常构建
init函数适合做什么,不适合做什么
init 的核心用途是做“不可推迟的一次性设置”,比如注册驱动、预热全局变量、读取只读配置。它不适合做可能失败或需要错误处理的逻辑——因为 init 无法返回错误,panic 会直接终止程序启动。
典型适用场景:
- 向
database/sql注册驱动:sql.Register("mysql", &MySQLDriver{}) - 初始化全局 sync.Once 或 sync.Map
- 解析嵌入的
embed.FS并缓存结构化数据
应避免的操作:
- 打开数据库连接(失败只能 panic,无法重试或降级)
- 读取外部配置文件(路径不存在或权限不足 → crash)
- 启动 goroutine 做长期任务(容易与 main 启动竞争,且无上下文控制)
多个 init 函数共存时的调试技巧
当一个包有多个 init 函数(分散在不同文件),出问题时很难定位执行顺序和状态。Go 不提供内置日志钩子,但可以用简单手段辅助排查:
- 在每个
init开头加fmt.Printf("[init] %s\n", "filename")(仅开发阶段,上线前删掉) - 用
runtime.Caller(0)获取当前文件行号,比硬编码字符串更可靠 - 若怀疑某
init修改了不该动的全局变量,可在其前后打点记录值,例如:log.Printf("before: %+v", cfg)
注意:不要在 init 中使用 log 初始化自身(如调用 log.SetOutput),这可能破坏标准库其他组件的日志行为。
init 和包级变量初始化的执行关系
包级变量声明时的初始化表达式,会在对应 init 函数之前执行。也就是说,var a = expensiveFunc() 实际上比同文件里的 func init() { ... } 更早运行。
这意味着:
- 不能在变量初始化表达式里引用尚未声明的变量(编译报错),但可以在
init里安全赋值 - 如果
expensiveFunc()依赖某个在init中才设置的配置项,就会出错——必须把该逻辑移到init内部 - 常量(
const)不受影响,它们在编译期确定,不参与运行时初始化流程
最容易被忽略的是:包级变量初始化失败(比如 panic)会导致整个包加载失败,且错误堆栈里不会显示 init 函数名,只显示变量名和文件位置。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











