
go 程序启动时会自动执行所有包级 init 函数,即使未显式调用;本例中注释掉 init 会导致全局 db 变量为 nil,后续数据库操作触发空指针解引用 panic。
go 程序启动时会自动执行所有包级 init 函数,即使未显式调用;本例中注释掉 init 会导致全局 db 变量为 nil,后续数据库操作触发空指针解引用 panic。
在 Go 语言中,init() 函数具有特殊的生命周期语义:它由运行时在 main() 执行前自动调用,且每个包内可定义多个 init 函数(按源码顺序执行)。它不接受参数、无返回值,不能被显式调用或引用——其存在意义就是完成包级初始化工作。
本例中,init() 承担了关键职责:初始化全局数据库连接对象 Db *sql.DB。一旦将其注释掉,Db 将保持零值 nil。随后在 Post.Create() 或 GetPost() 中调用 Db.QueryRow(...) 时,database/sql 包内部方法(如 (*DB).conn)会尝试对 nil 指针进行解引用,从而引发经典的 panic: runtime error: invalid memory address or nil pointer dereference。
⚠️ 注意:该 panic 并非发生在你调用 Db.QueryRow 的那一行,而是深入到标准库内部(见堆栈中 database/sql/sql.go:781),因此错误信息看似“不直观”,实则根源明确——缺少必要的初始化。
正确做法是保留 init(),并增强健壮性:
func init() {
var err error
Db, err = sql.Open("postgres", "user=gwp dbname=gwp sslmode=disable")
if err != nil {
panic(fmt.Sprintf("failed to open database: %v", err))
}
// 推荐:验证连接有效性(可选但强烈建议)
if err = Db.Ping(); err != nil {
panic(fmt.Sprintf("failed to ping database: %v", err))
}
}
此外,还需注意:
-
sql.Open仅创建连接池配置,并不立即建立物理连接;Db.Ping()可主动触发一次连接测试; - 全局变量
Db应确保在任何业务逻辑使用前已完成初始化; - 若需延迟初始化或支持多环境配置,可改用
sync.Once+ 函数封装,但init()仍是简单场景下最直接、最符合 Go 惯例的方式。
总结:init() 不是“可有可无”的占位符,而是 Go 初始化阶段的强制契约。删除它如同拔掉程序的电源线——表面代码结构完整,实际运行时因核心依赖缺失而瞬间崩溃。










