Go 中 init() 何时触发?通过示例解析

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

Go 中 init() 何时执行?通过实际示例深入解析



想象一下:你正在用 Go 编写一个微服务。在其中一个包中,你声明了 init() 来加载配置。启动应用程序——它却突然因 nil pointer dereference 而崩溃。你惊慌失措:“为什么?配置明明应该先加载的!”



熟悉吗?如果是,那你并不孤单。“init() 函数何时运行?” 这个问题是 Stack Overflow 上最热门的问题之一(有数千次浏览)。让我们一劳永逸地弄清楚 Go 中 init() 何时以及按何种顺序触发,这样你就不会再掉进这个陷阱了。



什么是 init(),它有什么用?



在 Go 中,init() 是一个没有参数和返回值的特殊函数。它不会被显式调用。Go 运行时会自动在 main() 函数执行之前调用它。



这有什么用?



  • 初始化需要复杂逻辑的全局变量(例如,从文件读取)。
  • 注册数据库驱动或 HTTP 处理器。
  • 检查环境(环境变量、资源可用性)。


但最重要的规则是:你永远不应该依赖同一包中不同文件之间 init() 的执行顺序。原因如下。



init() 究竟何时触发?精确顺序



Go 按照严格规则执行 init()。让我们逐一分解。



规则 #1:同一包内,顺序固定

在同一个包内,init() 按照文件声明的顺序执行(按文件名的字母顺序)。但这是实现细节——最好不要依赖它。



规则 #2:先初始化变量,再执行 init()

在调用 init() 之前,Go 会初始化包中的所有全局变量。如果某个变量依赖于同一包中的另一个变量,那么它们的声明顺序就很重要。



package config

var AppName string = "MyApp"var Version string = getVersion() // 在 init() 之前调用

func init() { // 这里 AppName 和 Version 已经可用 println("Init started for", AppName)}

func getVersion() string { return "1.0.0"}


规则 #3:导入的包先初始化

这是最重要的规则。如果包 A 导入了包 B,那么包 B 中的所有 init() 会先执行,然后才是包 A 中的。Go 会构建依赖图并“自底向上”执行初始化。



// 包 databasepackage database

func init() { println("1. database init")}

// 包 main(导入 database)package main

import "myapp/database"

func init() { println("2. main init")}

func main() { println("3. main function")}

// 输出:// 1. database init// 2. main init// 3. main function


规则 #4:一个文件中可以有多个 init()——但没必要

一个文件中可以有多个 init()。它们会按声明顺序执行。但这会使代码难以阅读。最好将逻辑合并到一个函数中。



实际示例:如果顺序搞错了会怎样?



让我们模拟一个典型的新手错误。假设我们有一个 config 包,它从文件读取设置,以及一个 app 包,它使用这些设置。



// config.gopackage config

var Settings map[string]string

func init() { // 模拟读取文件 Settings = map[string]string{ "port": "8080", "host": "localhost", } println("Config loaded")}

// app.gopackage app

import "myapp/config"

var ServerPort string

func init() { // 错误:如果 config.Settings 尚未初始化

博客

书籍推荐