init函数最适合用于程序启动前的基础准备工作,如配置加载、全局初始化、注册回调等。其执行顺序遵循依赖包优先、变量赋值先于init函数、多个init按声明顺序执行的原则。注意事项包括避免耗时操作、循环依赖、过度使用及测试中的副作用。实际建议用于全局注册和初始化,而非业务逻辑控制或直接panic。

init函数在Go语言中是个有点“神秘”但非常实用的存在。它的主要作用是进行包级别的初始化操作,比如设置变量、检查环境、加载配置等。你可能不需要每天用它,但在一些场景下,init能帮你把事情做得更优雅。

下面从几个实际使用角度来聊聊init的作用和执行顺序问题。

init函数用来做什么最合适?
init函数最合适的用途是做那些需要在程序启动时就完成的准备工作,而且这些工作通常跟具体业务逻辑无关,而是为后续代码运行打基础。
立即学习“go语言免费学习笔记(深入)”;
举个例子:

- 数据库连接池的初始化
- 配置文件的加载
- 注册某些全局的回调或处理器
- 初始化日志系统
这些任务一般只需要执行一次,而且要在main函数开始之前准备好。这时候init就能派上用场了。
需要注意的是:同一个包里可以有多个init函数,它们会按照声明顺序依次执行。
包初始化顺序到底是怎么算的?
Go的包初始化顺序其实有一套明确的规则,简单来说就是:
- 先初始化依赖的包
- 再初始化当前包里的变量赋值
- 最后执行当前包的init函数
举个简单的例子:
假设有三个包:
a.go依赖
b.go,而
b.go又依赖
c.go。
那么初始化顺序是:
- 先初始化 c
- 然后是 b
- 最后才是 a
并且每个包内部的初始化流程是:
- 包级变量赋值(按声明顺序)
- 所有的init函数(也按声明顺序)
也就是说,如果你在一个包里写了:
var x = initX()
func initX() int {
fmt.Println("x init")
return 0
}
func init() {
fmt.Println("first init")
}
func init() {
fmt.Println("second init")
}输出会是:
x init first init second init
所以变量初始化先于init函数,多个init则按出现顺序执行。
init函数使用的常见注意事项
虽然init函数很方便,但也有一些容易踩坑的地方:
- 不要在里面做太重的操作,比如网络请求、数据库查询等,因为这些操作失败会导致整个程序启动失败。
- 避免循环依赖,比如A包引入B包,B包又反过来引入A包,这会导致初始化死锁。
- 别过度使用init,如果只是某个结构体的初始化,不如提供一个NewXXX函数让用户显式调用更清晰。
- 测试时注意init副作用,有时候单元测试导入了一个包就会触发init,影响测试结果。
实际开发中init函数的使用建议
在项目实践中,init的使用建议如下:
- ✅ 用于注册、初始化全局状态(比如插件注册、配置加载)
- ✅ 多个init函数可拆分职责,保持逻辑清晰
- ❌ 不要用init做业务逻辑判断或复杂流程控制
- ❌ 避免在init中直接panic,除非真的无法继续运行
比如标准库中的
image/png包就在init里注册了解码器:
func init() {
image.RegisterFormat("png", pngHeader, Decode, DecodeConfig)
}这种做法很典型也很干净。
基本上就这些。init函数不复杂,但要把它用好,得理解清楚它的生命周期和执行顺序。合理使用可以让初始化过程更简洁,滥用则可能带来调试困难和维护成本。










