Repository navigation
Home
dig — Compile-time Dependency Injection for Go
dig is a compile-time code generation based dependency injection tool for Go. It combines the elegant API of Uber Fx with the compile-time safety of Google Wire, offering zero runtime reflection and zero runtime dependency.
- Core Features
- Comparison with Wire / Fx
- Installation
- Minimal Example
- Core API
- Named Instance Injection
- Modular Example
- Migration from Wire
- Migration from Fx
- FAQ
| Feature | Description |
|---|---|
| Compile-time resolution | Dependency graph resolved during go generate; errors caught at generation time. |
| Zero runtime reflection & zero runtime dependency | Generated code is plain Go with no extra imports. |
| Minimal API | Just Build, Provide, Supply, Invoke, Module. |
| Closure capture safety | Inline closures cannot capture locals from InitApp; enforced by generator. |
| Generic-aware | Supports generic functions and types natively. |
| Observability | Debug logging with runtime-overridable Logf. |
| Unused-provider policies |
error (default), ignore, or drop. |
| Module nesting | Compose modules hierarchically with built-in duplicate detection. |
| Named instance injection | Inject multiple instances of the same type by distinguishing them via parameter names. |
| Feature | dig | Google Wire | Uber Fx |
|---|---|---|---|
| Approach | Code generation | Code generation | Runtime reflection |
| Zero reflection | ✅ | ✅ | ❌ |
| Zero runtime dependency | ✅ | ✅ | ❌ (requires fx runtime) |
| Validation timing | Generation | Generation | Runtime (panic) |
| Direct value injection | ✅ dig.Supply (any expr) |
wire.Value (const only) |
✅ fx.Supply
|
| Closure capture safety | ✅ enforced | ❌ silently breaks | N/A |
Built-in Invoke |
✅ | ❌ | ✅ |
| Module definition | func Module() dig.Option |
var Set = wire.NewSet(...) |
fx.Module("name", ...) |
| Module nesting | ✅ explicit | ✅ explicit with names | |
| Generic support | ✅ compile-time native | ✅ reflection | |
| API ergonomics | Fx-style, minimal | Wire-style, verbose & counter-intuitive | Fx-style, minimal |
| Multiple instances of same type | ✅ Named parameters | ❌ Not supported (must use wrapper types) | ✅ Value Groups |
| Aspect | dig | Google Wire | Uber Fx |
|---|---|---|---|
| Multiple instances support | ✅ Native | ❌ Not supported | ✅ Native |
| Implementation mechanism | Parameter name resolution | Requires manual type OtherFoo Foo wrappers |
Value Groups via group:".." tags |
| Consumption | Inject by parameter name | Inject by wrapped type | Inject as slice of all instances |
| Ease of use | ⭐⭐⭐⭐⭐ Most intuitive | ⭐⭐⭐ Cumbersome and error-prone | ⭐⭐⭐⭐ Feature-rich but requires extra tagging |
| Example | func(mainDB, reportDB *sql.DB) |
Must use type ReportDB *sql.DB
|
fx.Provide(fx.Annotated{Group: "db", Target: ...}) |
- No need to repeatedly combine
wire.NewSet– just usedig.Moduleor flatten. -
dig.Supplyworks with any expression, not just compile-time constants. - No
wire.Bindfor interface binding – constructors can directly return interface types. - No dummy
return nil, nilplaceholder afterwire.Build. - Native generic support without extra wrappers.
-
Built-in
dig.Invoke– no need to manually orchestrate startup logic. - Closure capture safety – the generator enforces rules that Wire silently ignores.
- Multiple instances of the same type – Wire does not support this natively.
- No runtime reflection – faster startup and no runtime panic risks.
-
Zero runtime dependency – generated code doesn't import
dig, smaller binaries. -
Errors exposed during
go generate, not at runtime. -
Modules are unnamed –
dig.Moduleis purely for code organization, no name strings. -
More intuitive multi-instance injection – dig uses parameter names; Fx requires
grouptags.
go get github.com/shanjunmei/dig@v1.0.11
go install github.com/shanjunmei/dig/cmd/digen@latestRequires Go 1.21+.
//go:build digen
//go:generate go run -mod=mod github.com/shanjunmei/dig/cmd/digen -out di_gen.go
package main
import (
"context"
"github.com/shanjunmei/dig"
)
func InitApp() func(context.Context) error {
return dig.Build(
dig.Provide(NewConfig),
dig.Provide(NewDB),
dig.Supply(DefaultTimeout), // inject a value
dig.Provide(func(t Timeout) *Server { return NewServer(t) }),
dig.Invoke(func(srv *Server) error { return srv.Run() }),
)
}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)
}
}digen ./... # or go generate ./...
go run .| Function | Purpose |
|---|---|
dig.Build(...Option) func(context.Context) error |
Assemble container; returns runnable function. |
dig.Provide(any) Option |
Register a constructor (returns a value). |
dig.Supply(any) Option |
Inject an existing value directly (any expression, runtime-safe). |
dig.Invoke(any) Option |
Run a function after all providers are ready. |
dig.Module(...Option) Option |
Group options into reusable, nestable module. |
-
Closure capture restriction: Closures in
Provide/Invokemust not capture local variables fromInitApp. Only package-level symbols and literals are allowed. -
External parameters are auto-supplied: Parameters of
InitAppbecome available in the container automatically. -
Generics must be instantiated explicitly: e.g.,
dig.Provide(NewStore[int]). - Type conflicts: Use distinct types or named instances to disambiguate multiple values of the same underlying type.
dig supports injecting multiple instances of the same type by differentiating them through parameter names. This is useful for scenarios like:
- Multiple database connections (primary, replica, reporting)
- Multiple Redis clients for different business domains
- Multiple HTTP clients with different configurations
- Multiple RPC clients connecting to different services
- Define a provider with named return values – the names become the “instance names”.
- Depend on a specific instance by using the same parameter name in your consumer function.
// Provider returns two *sql.DB instances with different names
dig.Provide(func() (mainDB *sql.DB, reportDB *sql.DB, error) {
main, err := connectMain()
if err != nil { return nil, nil, err }
report, err := connectReport()
if err != nil { return nil, nil, err }
return main, report, nil
})
// Consumer uses the main database
dig.Invoke(func(mainDB *sql.DB) {
// mainDB is automatically injected
})
// Consumer uses the report database
dig.Invoke(func(reportDB *sql.DB) {
// reportDB is automatically injected
})You can also supply named values directly:
dig.Supply(mainDB) // variable name becomes instance name
dig.Supply(reportDB)The generator uses the variable name (not the type) to distinguish instances.
If multiple instances exist for the same type and a consumer does not specify a parameter name, the generator will produce an error listing the available names.
ambiguous dependency: multiple providers for type *sql.DB available:
- mainDB
- reportDB
- Existing code that uses a single instance of a type remains unchanged.
- The feature is additive – no breaking changes.
project/
├── user/
│ ├── module.go # user.Module()
│ └── repository/
│ └── module.go # repository.Module()
├── role/
│ ├── module.go # role.Module()
│ └── repository/
│ └── module.go
└── app/
└── app.go # InitApp() assembles all modules
package repository
import "github.com/shanjunmei/dig"
func Module() dig.Option {
return dig.Module(
dig.Provide(NewRepository[string]),
dig.Invoke(func(r *Repository[string]) { r.Print() }),
)
}package user
import (
"github.com/shanjunmei/dig"
"project/user/repository"
)
func Module() dig.Option {
return dig.Module(
dig.Provide(NewStore[int]),
repository.Module(), // nested sub-module
dig.Provide(func() string { return "user-module" }),
dig.Invoke(ProcessStore[int]),
)
}//go:build digen
package app
import (
"context"
"github.com/shanjunmei/dig"
"project/user"
"project/role"
)
func InitApp() func(context.Context) error {
return dig.Build(
user.Module(), // user module (includes its sub-modules)
role.Module(), // role module (includes its sub-modules)
dig.Invoke(func(s *user.Store[string]) error {
// startup logic
return nil
}),
)
}//go:build wireinject
package main
import "github.com/google/wire"
func InitApp() *App {
wire.Build(
NewConfig,
NewDB,
wire.Value(DefaultTimeout), // const only
wire.Bind(new(Logger), new(*MyLogger)),
)
return nil // dummy placeholder
}//go:build digen
//go:generate digen ./...
package main
import (
"context"
"github.com/shanjunmei/dig"
)
func InitApp() func(context.Context) error {
return dig.Build(
dig.Provide(NewConfig, NewDB),
dig.Supply(DefaultTimeout), // any expression
dig.Provide(func() Logger { return &MyLogger{} }),
dig.Invoke(func(app *App) error { return app.Start() }),
)
}| Step | Wire | dig |
|---|---|---|
| 1 | wire.NewSet(a, b) |
dig.Module(a, b) or flatten |
| 2 | wire.Value(const) |
dig.Supply(any) — works with any expression |
| 3 | wire.Bind(iface, impl) |
Constructor returns the interface directly |
| 4 |
wire.Build + return nil
|
dig.Build returns func(ctx) error directly |
| 5 | Manual startup call | Move to dig.Invoke for automatic execution |
| 6 | //go:build wireinject |
//go:build digen + //go:generate digen ./...
|
| 7 | wire gen |
digen ./... |
package main
import "go.uber.org/fx"
func main() {
app := fx.New(
fx.Provide(NewConfig, NewDB),
fx.Supply(DefaultTimeout),
fx.Invoke(func(srv *Server) { srv.Run() }),
)
app.Run()
}// di.go
//go:build digen
//go:generate digen ./...
package main
import (
"context"
"github.com/shanjunmei/dig"
)
func InitApp() func(context.Context) error {
return dig.Build(
dig.Provide(NewConfig, NewDB),
dig.Supply(DefaultTimeout),
dig.Invoke(func(srv *Server) error { return srv.Run() }),
)
}
// main.go
func main() {
if err := InitApp()(context.Background()); err != nil {
panic(err)
}
}| Step | Fx | dig |
|---|---|---|
| 1 | fx.New(...) |
dig.Build(...) returns func(ctx) error
|
| 2 | app.Run() |
Call the returned function with a context
|
| 3 | fx.Module("name", ...) |
dig.Module(...) — remove the name |
| 4 |
OnStart/OnStop hooks |
Move to dig.Invoke and handle explicitly |
| 5 | No build tags | Add //go:build digen + //go:generate digen ./...
|
| 6 | Imports go.uber.org/fx
|
Run digen, remove fx dependency |
Because dig lifts closures to package-level functions during generation; captured locals would become invalid. Use package-level variables or pass values as parameters (they are auto-supplied).
Run digen -debug. The generated code will include Logf calls. At runtime, override Logf to see the resolution steps.
Yes. di_gen.go is plain Go with no external imports. You can commit it, and others can build without installing dig.
Define distinct types to disambiguate, e.g., type UseCache bool and type UseDB bool. Alternatively, use named instance injection (see the dedicated section) to inject multiple instances of the same type via parameter names.
shanjunmei/dig is a compile-time generator producing code with zero runtime dependency. uber-go/dig is a runtime reflection container.
Yes. dig uses parameter names instead of tags, which is more intuitive and doesn't require annotating your providers. See the Named Instance Injection section for details.
Google Wire requires each type to have exactly one provider. If you need multiple instances, you must define wrapper types (e.g., type MainDB *sql.DB and type ReportDB *sql.DB). This is verbose and error-prone.