init 和 defer
2026年9月6日
两个特殊机制:init 在 main 之前自动跑,defer 把清理工作留到函数返回前。
init 函数
init 不能手写调用,也不能当参数传来传去。没有参数、没有返回值。main 开始之前,运行时会按依赖顺序自动执行各个包的 init。
package main
import "fmt"
func init() {
fmt.Println("先 init")
}
func main() {
fmt.Println("后 main")
}
go
同一个文件可以写多个 init,写在前面的先跑。同一个包里多个文件的顺序,编译器按文件名排序后再跑 init。
跨包时:先初始化被 import 的包,再初始化当前包。一个包只初始化一次,就算被 import 多次。
包级变量的初始化在该文件的 init 之前。大致顺序:
- 先把依赖包装好
- 再算本包的包级变量
- 再跑本包的
init - 最后进
main
init 适合注册驱动、检查环境;别在里面做又慢又容易失败的网络请求,出错很难排查。
defer 函数
defer 登记一次调用,真正执行是在函数 return 之前。文件、锁、成对的「打开 / 关掉」都适合用它。
f, err := os.Open("a.txt")
if err != nil {
return err
}
defer f.Close()
go
多个 defer 是栈:后登记的先执行,离 return 近的先跑。
func order() {
defer fmt.Println(1)
defer fmt.Println(2)
fmt.Println(0)
}
// 打印:0 2 1
go
defer 后面那个调用,参数在登记时就算好。下面会打印 1,不是 2:
n := 1
defer fmt.Println(n)
n = 2
go
如果要看「返回那一刻」的值,包一层闭包:
n := 1
defer func() { fmt.Println(n) }()
n = 2
// 打印 2
go
defer 能改命名返回值。和 recover 搭配可以接住 panic,异常那一课再写。