dig 编译期依赖注入 for Go

Fx 风格极简 API + Wire 风格代码生成
零运行时反射 · 零运行时依赖 · 编译期安全

di.go
//go:build digen

func InitApp() func(context.Context) error {
    return dig.Build(
        dig.Provide(NewConfig),
        dig.Provide(NewDB),
        dig.Supply(DefaultTimeout),
        dig.Invoke(func(srv *Server) error { return srv.Run() }),
    )
}

为什么选择 dig?

Go 的依赖注入工具分为两大阵营,dig 取其精华,去其糟粕。

Uber Fx
✓ API 优雅(Provide / Invoke / Supply / Module)
✗ 运行时反射 — 启动慢、运行时 panic
✗ 二进制体积更大
Google Wire
✓ 编译期安全,零运行时开销
✗ API 冗长且反直觉
✗ 已归档,不再维护
dig 两者优点
✓ Fx 风格极简 API
✓ Wire 风格代码生成(无反射)
✓ 原生泛型 + 命名实例注入
✓ 闭包捕获安全 + 可操作错误

核心特性

12 项核心能力,覆盖 Go 依赖注入的完整场景

⚡

编译期解析

依赖图在 go generate 期间完成解析,错误在生成阶段即被捕获,不等到运行时。

📦

零运行时依赖

生成的代码是纯 Go,不导入任何额外包,零反射、零开销。

🔑

极简 API

仅需 Build、Provide、Supply、Invoke、Module 五个函数。

🛡️

闭包捕获安全

内联闭包不能捕获 InitApp 中的局部变量,由生成器强制检查。

🔄

闭包内联

-inline 仅将简单闭包内联为 IIFE。身份闭包无论是否传 -inline 都会塌缩为直接类型转换。

🧬

泛型支持

原生支持泛型函数和类型,显式实例化即可:dig.Provide(NewStore[int])

🏷️

命名实例注入

通过参数名区分同一类型的多个实例,支持多 DB 连接、多 Redis 客户端等场景。

🔍

可观测性

调试日志运行时可覆盖 Logf,-debug 打印导入别名映射。

💡

可操作错误

所有错误消息包含源码位置 file:line:col 与 💡 Fix: 修复建议。

🧩

模块嵌套

层次化组合模块,内置重复检测,复用性更强。

⚙️

未使用提供者策略

三种模式:error(默认)、ignore、drop,灵活控制。

🔒

ShadowGuard 保护

变量名遮蔽保护机制,自动检测并避免生成代码中的命名冲突。

快速开始

三步上手 dig

1

安装

terminal
go get github.com/shanjunmei/dig@latest
go install github.com/shanjunmei/dig/cmd/digen@latest
2

编写 DI 规格文件 di.go

di.go
//go:build digen
package main

import (
    "context"
    "github.com/shanjunmei/dig"
)

//go:generate go run -mod=mod github.com/shanjunmei/dig/cmd/digen -out dig_gen.go

func InitApp() func(context.Context) error {
    return dig.Build(
        dig.Provide(NewConfig),
        dig.Provide(NewDB),
        dig.Supply(DefaultTimeout),
        dig.Provide(func(t Timeout) *Server { return NewServer(t) }),
        dig.Invoke(func(srv *Server) error { return srv.Run() }),
    )
}
3

编写业务逻辑 main.go

main.go
package main

import "context"

type Config struct{ Addr string }
func NewConfig() *Config { return &Config{Addr: ":8080"} }

type DB struct{}
func NewDB(*Config) *DB { return &DB{} }

type Timeout int
var DefaultTimeout Timeout = 5

type Server struct{}
func NewServer(Timeout) *Server { return &Server{} }
func (*Server) Run() error { return nil }

func main() {
    if err := InitApp()(context.Background()); err != nil {
        panic(err)
    }
}
4

生成并运行

terminal
digen ./...   # 或 go generate ./...
go run .

关键约束

digen 在生成期强制的约束——违反时会给出清晰错误与 💡 Fix: 建议,而非晦涩的编译失败

DI 规格文件必须带 //go:build digen

每个包含 dig.Build(...) 调用的文件都必须带 //go:build digen 约束。digen 在生成的 dig_gen.go 上写死 //go:build !digen;若源文件不带对应标签,正常 go build 会同时编译两个文件并报 InitApp redeclared。digen 现在在生成期强制校验,直接给出清晰错误与 💡 Fix:,而非让重声明错误推迟暴露。

context.Context 仅限 Invoke 使用

provider(通过 dig.Provide / dig.Supply / dig.Module 注册的构造函数)不得声明 context.Context 参数。provider 在 InitApp 内被即时(eager)解析,早于运行时 context.Context 的产生,故该参数在生成代码中必然 undefined。context 注入只对 dig.Invoke(func(ctx context.Context) { ... }) 合法。digen 在生成期拒绝 provider 侧的 context.Context 参数,并给出指向 dig.Invoke 或 dig.Supply 的 💡 Fix:。

di.go 只放接线

带 //go:build digen 的 di.go 只能包含 dig.Build(...) 接线(Provide / Invoke / Supply / Module 调用)。所有被接线引用的领域类型、构造函数、包级变量必须定义在不带该构建标签的文件(例如 types.go)或导入的包里。原因:生成的 dig_gen.go 带 //go:build !digen,正常 go build(不带 digen 标签)时 di.go 被排除,其内符号对生成代码不可见,会导致晦涩的 undefined: X。digen 现在在写文件之前就做契约预检(checkContractVisibility):一旦接线引用了定义在 digen 文件中的主包符号,立即中止并给出清晰错误与 💡 Fix:,而不是事后由类型检查兜底。

生成安全网

生成代码后,digen 会对产出的 dig_gen.go 做一次 go/types 类型检查。由于用户源码在加载阶段已通过完整类型检查,生成文件上的类型错误只有两类:(a) 真正的内部生成器 bug;(b) 漏过契约预检的 digen 契约违规(仅发生在 IR 缓存命中、预检被跳过时)。两者都会中止写文件;契约违规给出与预检一致的 💡 Fix: 指引,内部 bug 则输出可点击的预填 GitHub issue 链接(外加可复制模板)以便上报——绝不会静默产出无法编译的文件。

核心 API

五个函数,覆盖全部依赖注入场景

dig.Build(...Option) func(context.Context) error
组装容器,返回可执行函数。入口函数。
dig.Provide(any) Option
注册构造函数(返回一个值,可选 error)。
dig.Supply(any) Option
直接注入一个已有值(任意表达式,运行时安全)。
dig.Invoke(any) Option
在所有提供者就绪后执行一个函数(可返回 error)。
dig.Module(...Option) Option
将多个选项组合为可复用、可嵌套的模块。

命名实例注入

通过参数名区分同一类型的多个实例

named_instances.go
// 提供者返回两个不同名称的 *sql.DB 实例
dig.Provide(func() (mainDB *sql.DB, reportDB *sql.DB, err error) {
    main, err = connectMain()
    if err != nil { return nil, nil, err }
    report, err = connectReport()
    return main, report, err
})

// 消费方通过参数名获取特定实例
dig.Invoke(func(mainDB *sql.DB) { // mainDB 自动注入 })
dig.Invoke(func(reportDB *sql.DB) { // reportDB 自动注入 })

CLI 参数

参数 默认值 说明
-outdig_gen.go输出文件名(./... 模式下忽略)
-unusederror未使用提供者的处理策略:error / ignore / drop
-debugfalse启用调试日志(详细错误始终显示)
-aliasfull导入别名策略:full / short / obfuscated / numeric
-inlinefalse仅将简单闭包内联为 IIFE;身份闭包始终塌缩为类型转换(与本 flag 无关)
-typechecktrue生成后类型检查产出代码以捕获内部生成器 bug;大型 ./... 运行可关闭(-typecheck=false)以省去逐文件重载包图。
-cachefalse将提取出的 IR 缓存到磁盘,未改动包命中缓存时跳过提取/类型检查。
-cachedir""IR 缓存目录(默认:os.TempDir()/digen-ir-cache;仅 -cache 设置时生效)。
-versionfalse打印版本信息并退出

CLI 命令

除默认生成运行(digen [packages...])外,digen 还提供用于脚手架、校验与检视的子命令。

命令说明
digen init [path]生成带 dig.Build 入口的 di.go 脚手架
digen check [pkgs]校验 DI 契约,不写文件
digen graph [pkgs]以 Mermaid 打印提供者依赖图
digen explain <type> [pkgs]解释某类型/提供者的解析路径
digen completion <shell>输出 shell 补全脚本(bash/zsh/fish)

对比矩阵

dig vs Google Wire vs Uber Fx — 全维度对比

特性digGoogle WireUber Fx
方法代码生成代码生成运行时反射
需要代码生成步骤✅ digen✅ wire CLI❌
零反射✅✅❌
零运行时依赖✅✅❌(依赖 fx + dig 运行时)
校验时机生成期生成期运行时
提供者初始化即时即时惰性
二进制体积影响极小极小中等
特性digGoogle WireUber Fx
核心 API 数量5 个7 个15+ 个
直接值注入✅ 任意表达式⚠️ 禁止函数调用✅ 仅具体类型
内置 Invoke✅❌✅
模块嵌套✅ 显式⚠️ 扁平组合✅ 显式带命名
相同类型多实例✅ 命名参数❌ 需包装类型✅ 命名 + 值组
泛型支持✅ 编译期❌⚠️ 仅已实例化
闭包捕获安全✅ 生成器强制N/AN/A
API 友好度Fx 风格极简冗长反直觉Fx 风格极简
特性digGoogle WireUber Fx
错误传播模型Provider panic / Invoke 返回error 返回值传播app.Err() + 回滚
错误含源码位置✅ file:line:col⚠️ 仅名称⚠️ 运行时堆栈
可操作修复建议✅ 💡 Fix:❌❌
未使用提供者策略3 种模式仅硬错误N/A(惰性)
不运行即可校验✅ 生成即校验✅✅ ValidateApp
调试日志✅ 可覆盖 Logf❌✅ fxevent
特性digGoogle WireUber Fx
App 生命周期对象❌ 裸 func❌ 生成值✅ *fx.App
信号处理❌ 调用方负责❌✅ 内置
编程式关停❌❌✅ Shutdowner
生命周期钩子❌❌✅ OnStart/OnStop
装饰器❌❌✅ Decorate/Replace
特性digGoogle WireUber Fx
维护状态✅ 活跃⚠️ 已归档✅ 活跃
最新版本v1.0.24v0.7.0 (beta)v1.24.0
Go 版本要求1.22+标准1.22+
重构友好度高低中

示例

28 个示例包 — 1 个可运行 example 二进制 + 库 fixture;gen_failures/ 下 35 个负向测试包(含 build-tag 变体)

✅ 成功示例(13 个)

  • app — 完整应用:跨包依赖、泛型、命名实例、闭包
  • app_basic — 基础用法:Provide + Supply + Invoke
  • app_debug — 调试日志与可观测性
  • app_edge — 边缘场景:包装类型、自由变量
  • app_xpkg_generic — 跨包泛型:cache.Cache[*common.Config]
  • app_runtime_err — 运行时错误传播路径
  • app_gen_test — 生成代码测试
  • closure_param — 闭包参数与局部变量(提升闭包中允许使用)
  • closure_scope_locals — 闭包内各类变量定义(range Key/Value、嵌套 FuncLit 参数、type switch guard、if 初始化、命名返回值)
  • context_alias — context 别名注入
  • shadow_err — 变量遮蔽报错场景
  • shadow_freevar — 自由变量遮蔽
  • supply_param — dig.Supply 传入模块参数

❌ 失败示例(gen_failures/ 下 36 个负向测试包;含 build-tag 变体,以下列举代表性子集)

  • ambiguous — 命名实例歧义
  • capture_const / capture_ctx — 闭包捕获违规
  • closure_capture — 局部变量捕获禁止
  • closure_shadow_outer — 同名遮蔽:外层的非法引用仍必须报错
  • closure_private_fn — 提升闭包中的未导出跨包调用
  • control_flow — Module 内控制流禁止
  • cycle — 循环依赖检测
  • duplicate_* — 重复绑定(provide/param/named/supply)
  • private_visibility — 跨包私有函数引用
  • unused_provider — 未使用提供者
  • missing_provider — 缺少提供者
  • invalid_option / provide_option / supply_option / invoke_option — API 参数校验
  • provider_ctx — provider 声明 context.Context 参数
  • contract_digen_symbol — 接线引用定义在 //go:build digen 文件中的符号(契约违规)
  • ...及更多

依赖关系可视化

直观查看 digen 如何把你的提供者连接起来 —— 每一条依赖边都在编译期解析完成

Config DB Cache Repository App

运行 digen graph ./... 即可把完整依赖图以 Mermaid 格式打印出来 —— 复制到 mermaid.live 即可渲染。

terminal
digen graph ./...
# package github.com/you/app
flowchart TD
  Config["Config"] --> App
  DB["*DB"] --> Repository
  Cache["Cache"] --> Repository
  Repository["*Repository"] --> App

版本亮点

v1.0.24
2026-09-14

生成代码类型检查与闭包捕获修复

  • 修复导入别名泄漏:依赖的 import gotime "time" 不再泄漏进生成文件导入块(解决 undefined: time)
  • 消除别名自替换产生的 gogotime;多字节 UTF-8 标识符处理正确
  • 闭包可正常使用作用域内变量(range Key/Value、嵌套参数、type switch guard、命名返回值)不再误报;同名遮蔽漏报已修复
  • 闭包内引用的主包包级变量保留真实值(不再静默提升为 DI 形参)
v1.0.23
2026-09-04

恒等闭包识别修复与闭包分析简化

  • 恒等闭包(如 T(p)、&p、*p)现始终被正确识别为恒等转换并塌缩
  • 闭包分析代码简化并新增防御性检查;无运行时行为变化
v1.0.22
2026-08-27

最低 Go 版本降至 1.22 与生成期韧性增强

  • 最低 Go 版本降至 1.22.0(x/tools v0.30.0),可在更广的 Go 工具链上构建运行
  • 修复字符串常量双重转义;修正未使用 provider 检测
  • digen ./... 采用批处理类型检查(更快);写入阶段跳过失败包而非中断整轮
  • 数字别名确定性生成;digen check 部分失败非零退出;诊断更友好
v1.0.21
2026-08-25

缺陷修复与文档站语言切换修复

  • 闭包捕获跨包导出符号误报修复;IIFE 内联更稳健;生成器错误链修复
  • 文档站左侧导航(TOC)随语言切换实时跟随
  • 订正 -cache 文档:只跳过提取,类型检查仍每次执行
v1.0.20
2026-08-21

恒等闭包塌缩与 IIFE 内联解耦

恒等闭包(直接返回 / 取地址 / 解引用 / 类型转换 / 类型断言五种)现始终塌缩为内联表达式,与 -inline 无关;-inline 现仅控制 IIFE 内联(默认关)。新增类型断言恒等闭包 func(p any) T { return p.(T) }。

v1.0.19
2026-08-18

生成期契约预检与诊断增强

新增 digen CLI 子命令(init/check/graph/explain/completion);生成期契约预检确保 di.go 只放接线,一旦引用 //go:build digen 文件内定义的符号即中止生成并给出 💡 Fix:;生成后类型检查安全网区分契约违规与内部生成器 bug;构建约束校验收敛到 internal/buildconstraint 单一真源;新增 golden 文件逐字节回归测试,覆盖 11 个复杂 example。

v1.0.18
2026-08-15

生成期加固与诊断

provider 不可声明 context.Context 参数(context 注入仅限 Invoke);DI 规格文件的 //go:build digen 标签现于生成期强制校验;生成后 go/types 安全网在出现内部生成器 bug 时拒绝写出坏文件并输出可点击的预填 GitHub issue 链接;gen_failures/ 新增自动化回归测试。并修复 v1.0.17 引入的回归——跨包模块内联时闭包参数/局部变量被误报为 private("var X is private"),现已放行;类型名改写由脆弱的正则改为 AST 精确改写;新增可选的稳定可序列化 IR 磁盘缓存(-cache/-cachedir,默认关)。