dig � Compile-time Code Generation Zero-Reflection DI Container for Go
中文文档 | English Docs

Core Philosophy
Compile-time static safety, zero runtime reflection overhead, no runtime magic, fully native Go style.
All dependency resolution logic is generated into static Go code at build time, runtime only executes plain function calls.
Core Advantages
-
Zero runtime reflection
All dependency assembly logic is generated as static Go source code via digen CLI. No runtime type parsing, no reflection performance loss, startup performance equals handwritten initialization code.
-
Mutually exclusive build tag 3-file isolation (official standard spec)
Split project code into 3 independent files with exclusive build tags to eliminate duplicate definition / undefined symbol compile errors:
di.go Tag digen: Only parsed by digen generator, excluded from normal build & run
main.go No build tag: Shared base types, global variables, business models, entry main(), compiled in all scenarios
di_gen.go Tag !digen: Auto-generated runtime entry file, skipped during code generation
- Strict separation rules for Supply & Provide closures (core constraint)
dig.Supply: Natively accepts package-level global runtime variables as type providers
- Inline
dig.Provide(func() T) anonymous closure: Only constant literals are allowed as free variables; capturing local variables inside InitApp() will trigger generator error, avoid undefined symbol after code split
-
Wrapper type solves primitive type injection conflict
Direct injection of raw bool/string/time.Duration will cause duplicate provider conflicts. Define lightweight alias wrapper types for each business scenario, fully recognized and parsed by digen.
-
Native Invoke post-start callback
Dedicated dig.Invoke() API, all callback logic will be scheduled after all Supply & Provider instances are created. Designed for route registration, database initialization, service startup and other boot logic.
-
Earlier dependency error detection
Missing dependencies, circular dependencies, duplicate providers are fully validated during go generate; errors are exposed at development time instead of panicking online.
-
3 configurable unused provider policies
Support 3 modes to handle unused constructors via command flag --unused:
error: Generation fails and throws error if unused provider exists (default)
ignore: Keep unused provider with blank assignment _ = fn()
drop: Remove unused provider code directly from generated file
-
Minimal external dependencies
Core library only relies on Go standard library; digen generator only depends on official golang.org/x/tools AST toolkit, no heavy third-party dependencies.
-
Low learning cost
Only 4 core APIs: dig.Build, dig.Provide, dig.Supply, dig.Invoke; clear build tag & closure constraints, low entry barrier.
-
Tiny final binary size
No embedded reflection runtime, no redundant framework logic, compiled binary size is close to handwritten initialization code.
Mandatory Build Tag Isolation Rules (Must Follow)
File Split & Tag Matching Rules
di.go (Generator-only init logic, tag digen)
//go:generate go run -mod=mod github.com/shanjunmei/dig/cmd/digen -out di_gen.go -unused=drop --tags=digen
//go:build digen
// +build digen
package main
import (
"time"
"github.com/shanjunmei/dig"
)
func InitApp() *dig.App {
return dig.Build(
// Supply accepts global variables defined in main.go
dig.Supply(s),
// Provide closure only uses constant literals, no outer local variable capture
dig.Provide(func() EnableCache { return EnableCache(true) }),
dig.Provide(func() QueryTimeout { return QueryTimeout(3 * time.Second) }),
dig.Provide(NewConfig),
dig.Provide(func(t QueryTimeout, um UseMySQL, rm EnableCache) any {
if um {
return NewMySQLStore(t)
}
return NewRedisStore(rm)
}),
dig.Provide(NewServer),
// All boot logic executes after all instances are ready
dig.Invoke(func(srv *Server, um UseMySQL, ec EnableCache, t QueryTimeout) {
println("service boot complete")
println("mysql switch:", bool(um))
println("cache enable:", bool(ec))
println("query timeout sec:", t.Seconds())
_ = srv.Run()
}),
)
}
- Only contains
InitApp() with full dig.Build() chain
- Only parsed by digen during
go generate
- Skipped during normal
go run / go build due to tag mismatch
main.go (Shared base code, no build tag)
package main
import (
"context"
"time"
)
// Global runtime variable consumed by dig.Supply
var s = UseMySQL(true)
// Unique wrapper types to resolve primitive type injection collision
type UseMySQL bool
type EnableCache bool
type QueryTimeout time.Duration
// Business model & constructors
type Config struct {
Addr string
}
func NewConfig() *Config {
return &Config{Addr: "0.0.0.0:8080"}
}
type MySQLStore struct{}
func NewMySQLStore(t QueryTimeout) *MySQLStore {
return &MySQLStore{}
}
type RedisStore struct{}
func NewRedisStore(c EnableCache) *RedisStore {
return &RedisStore{}
}
type Server struct {
store any
}
func NewServer(store any) *Server {
return &Server{store: store}
}
func (s *Server) Run() error { return nil }
func main() {
app := InitApp()
_ = app.Run(context.Background())
}
- All custom wrapper types, business structs, global variables, program entry
main()
- Compiled under all build modes, visible to both generator and runtime
di_gen.go (Auto-generated runtime entry, tag !digen)
// Code generated by digen; DO NOT EDIT.
//go:generate go run -mod=mod github.com/shanjunmei/dig/cmd/digen -out di_gen.go -unused=drop --tags=digen
//go:build !digen
// +build !digen
package main
import (
"context"
"github.com/shanjunmei/dig"
"time"
)
// Auto-split top-level functions generated by digen for closures
func __p_1() EnableCache {
return EnableCache(true)
}
func __p_2() QueryTimeout {
return QueryTimeout(3 * time.Second)
}
func __p_4(t QueryTimeout, um UseMySQL, rm EnableCache) any {
if um {
return NewMySQLStore(t)
}
return NewRedisStore(rm)
}
func __i_6(srv *Server, um UseMySQL, ec EnableCache, t QueryTimeout) {
println("service boot complete")
println("mysql switch:", bool(um))
println("cache enable:", bool(ec))
println("query timeout sec:", t.Seconds())
_ = srv.Run()
}
// Runtime fallback InitApp, only compiled under !digen tag
func InitApp() *dig.App {
v0 := s
v1 := __p_1()
v2 := __p_2()
v4 := __p_4(v2, v0, v1)
v5 := NewServer(v4)
return dig.New(func(ctx context.Context) error {
__i_6(v5, v0, v1, v2)
return nil
})
}
- Automatically overwritten after every
go generate
- Provides runtime
InitApp() implementation for online execution
- Excluded during code generation due to conflicting
!digen tag with generator --tags=digen
Consequence of Incorrect Tag Configuration
Mismatched tags or mixed file logic will trigger fatal compile errors:
- Duplicate definition: Two
InitApp() functions exist in one package
- Undefined symbol: Generator cannot resolve base types / global variables
- Broken dependency topology parsing inside digen
Installation Guide
1. Install core library to project
go get github.com/shanjunmei/dig
go install github.com/shanjunmei/dig/cmd/digen@latest
Minimum Go version requirement: Go 1.21+
Generation & Runtime Commands
# Generate DI static code
go generate ./...
# Directly call digen tool
digen -out di_gen.go -unused=drop --tags=digen
# Normal compile & run program
go run .
Horizontal Comparison With Mainstream Go DI Frameworks
| Comparison Item |
dig (This Project) |
Google Wire |
Uber dig (Reflection Runtime) |
Uber FX (Reflection Runtime) |
| Require code generation |
✅ digen CLI tool |
✅ wire gen |
➖ No code generation workflow |
➖ No code generation workflow |
| Runtime reflection exists |
❌ Zero, pure static generated code |
❌ Zero, pure static generated code |
✅ Heavy runtime reflection |
✅ Heavy runtime reflection |
| Full dependency validation at generation time |
✅ Full link check (missing/circular dependency error at dev stage) |
✅ Full link check at generate time |
➖ No generate step, only dynamic runtime check |
➖ No generate step, only dynamic runtime check |
| Mutually exclusive build tag 3-file isolation spec |
✅ Official standard, native adapted by digen |
✅ Natively support build tag, self-implementable 3-file isolation |
➖ No code generation, no duplicate InitApp conflict, no need this spec |
➖ No code generation, no duplicate InitApp conflict, no need this spec |
| Supply native support for package global runtime variables |
✅ Top-level dig.Supply built-in API, out-of-box |
⚠️ Global var injection supported, no dedicated Supply syntax, verbose writing |
❌ No matching API, only constructor parameter injection |
❌ No matching API, only constructor parameter injection |
| Provide closure constraint: Only constant literals allowed, forbid capturing outer local variables |
✅ Tool forced validation, error thrown if capture local variables |
⚠️ No forced validation, arbitrary free variable capture allowed; risk of undefined symbol after generation |
➖ Anonymous closure Provide syntax unsupported |
➖ Anonymous closure Provide syntax unsupported |
| Wrapper type resolve primitive type injection collision |
✅ Official recommended standard solution, fully parsed by generator |
⚠️ Syntactically supported, manual type wrapping required, no auxiliary generator check |
❌ No built-in differentiation logic, identical primitive types cannot be distinguished |
❌ No built-in differentiation logic, identical primitive types cannot be distinguished |
| Native Invoke post-boot callback |
✅ Built-in dig.Invoke API dedicated to route register / service startup |
⚠️ No native API, massive boilerplate code required for manual encapsulation |
❌ Independent Invoke callback mechanism missing |
✅ Full built-in lifecycle system (Start/Hook) |
| Unused constructor control policy |
3 configurable modes: error / ignore / drop (controllable at generate time) |
⚠️ Silent drop only, no error alert mode |
➖ Runtime framework, no generate-time control logic, silent ignore |
➖ Runtime framework, no generate-time control logic, silent ignore |
| External dependency count of business core library |
Zero, only Go standard library |
Zero, only Go standard library |
Multiple third-party dependencies |
Massive heavy dependencies (log, event bus, full lifecycle components) |
| Learning curve |
Extremely low: only 4 core APIs + clear tag / Supply constraints |
Medium: simple base API, verbose closure & global var writing, many hidden pitfalls |
Medium: need master type tag & scope rules |
High: complex full lifecycle & module layered system |
| Final compiled binary size |
Tiny, no embedded reflection runtime logic |
Tiny, no embedded reflection runtime logic |
Medium, embedded reflection dispatch code |
Largest, built-in log, event bus, full lifecycle components |
Symbol Annotation Explanation
✅: Framework natively provides official standard API/spec, out-of-box, no extra adaptation cost
⚠️: Syntactically implementable, but tedious workflow with potential compile/runtime risks, no official simplified solution
❌: Framework underlying does not support this capability, no corresponding implementation logic
➖: Framework positioned as reflection-based runtime DI, no code generation workflow. This item is exclusive spec for code-generation tools; the framework naturally does not require this capability, not a functional defect.
Key Correction Description For Comparison Table
-
Build tag 3-file isolation
This spec is exclusive engineering standard for code-generation DI tools, used to separate generator parsing source (digen tag) and online runtime source (!digen tag), avoid duplicate definition of InitApp().
Google Wire natively supports Go build tag, developers can implement identical 3-file isolation manually.
Uber dig / FX have no code generation step, no two copies of InitApp() to conflict, so this isolation spec is unnecessary. The original table mark "not supported" is misleading and revised.
-
Runtime framework related items unified semantic adjustment
All code-generation exclusive features (generate-time validation, 3-file isolation, unused constructor control) are marked ➖ for Uber dig/FX, clarify it's positioning difference rather than function missing.
-
Objective description for Wire Supply & closure capture
Wire can pass global variables as dependencies, but has no independent top-level Supply function, writing is verbose. Meanwhile Wire has no free variable capture validation; capturing local variables inside InitApp will lead to undefined compile error in generated file, the risk mark is retained objectively.
-
Invoke capability boundary distinction
dig's Invoke is lightweight post-creation callback without heavy lifecycle overhead; FX provides complete lifecycle hook system. The two design targets are different, only objective description of capability form, no subjective pros and cons judgment.