中文文档 | English
📢 Version Note
- v1.0.4 – last release with
*dig.Appstruct- v1.0.5 –
InitApp()returnsfunc(context.Context) error, with zero runtime dependency See Upgrading from v1.0.4 for migration instructions.
dig is a code‑generation based dependency injection container for Go.
It resolves all dependencies at compile time and generates plain Go source code – no reflection, no runtime magic, just static, native Go.
- Zero reflection – startup performance equals hand‑written initialization code.
- Static safety – missing or circular dependencies are caught during
go generate, not at runtime. - Minimal API – only 4 core functions:
Build,Provide,Supply,Invoke. - Built‑in
Supply– inject package‑level global variables directly, no verbose wrappers. - Wrapper types – define lightweight aliases to resolve primitive type injection conflicts.
- Built‑in
Invoke– all startup/registration logic runs after all providers are ready. - Observability – optional debug logging with
before/aftermarkers;Logfcan be overridden at runtime. - Unused‑provider policies – choose
error(default),ignore, ordrop. - Zero runtime dependency – generated code does not import the
digpackage at runtime. - Small binary size – no embedded reflection or framework runtime.
- Low learning curve – just 4 APIs and a few clear constraints.
go get github.com/shanjunmei/dig@v1.0.5
go install github.com/shanjunmei/dig/cmd/digen@latestRequires Go 1.21+.
The simplest way to use dig involves two files.
This file is the only file you need to write for the DI wiring. It uses the //go:build digen build tag so it is only parsed during code generation.
//go:build digen
package main
import (
"context"
"fmt"
"github.com/shanjunmei/dig"
)
//go:generate go run -mod=mod github.com/shanjunmei/dig/cmd/digen -out di_gen.go
func InitApp() func(context.Context) error {
return dig.Build(
// 1) Ordinary function constructors
dig.Provide(NewConfig),
dig.Provide(NewDB),
// 2) Provide a value directly (useful for constants or globals)
dig.Supply(DefaultTimeout),
// 3) Provide using an inline closure – acceptable as long as
// it only uses constant literals or supplied globals.
dig.Provide(func(timeout Timeout) *Server {
return NewServer(timeout)
}),
// 4) Invoke runs after all providers are ready.
// It can return an error which will be propagated to the caller.
dig.Invoke(func(srv *Server) error {
return srv.Run()
}),
)
}This file contains all your types, constructors, and the main() function. It has no build tag.
package main
import (
"context"
"fmt"
"time"
)
// ---------- Config ----------
type Config struct {
Addr string
}
func NewConfig() (*Config, error) {
return &Config{Addr: ":8080"}, nil
}
// ---------- DB ----------
type DB struct {
cfg *Config
}
func NewDB(cfg *Config) (*DB, error) {
if cfg == nil {
return nil, fmt.Errorf("config cannot be nil")
}
return &DB{cfg: cfg}, nil
}
func (db *DB) Ping() error {
fmt.Println("DB ping OK")
return nil
}
// ---------- Timeout (wrapper type) ----------
type Timeout time.Duration
var DefaultTimeout = Timeout(5 * time.Second)
// ---------- Server ----------
type Server struct {
db *DB
timeout Timeout
}
func NewServer(timeout Timeout) *Server {
return &Server{timeout: timeout}
}
func (s *Server) Run() error {
fmt.Printf("Server running with timeout %v\n", s.timeout)
return nil
}
// ---------- Main ----------
func main() {
run := InitApp()
if err := run(context.Background()); err != nil {
fmt.Printf("app failed: %v\n", err)
}
}There are two ways to invoke the generator:
# Generate only the current package (default)
digen
# Generate for all sub‑packages recursively
digen ./...Or via go generate (if your di.go contains a //go:generate directive):
go generate ./...After generation, build and run your application:
go run .The generator outputs dig_gen.go in each package directory that contains a dig.Build call. Each generated file is self‑contained with no runtime dependency on the dig package.
You've already seen Supply in the basic example – it injects a package‑level variable. You can use it for any global, constant, or runtime‑computed value.
dig.Supply(globalDBConn)
dig.Supply(apiKey)If you need multiple bool, string, or time.Duration values, wrap them in distinct types to avoid duplicate provider errors.
// main.go
package main
type UseMySQL bool
type UseRedis bool
type QueryTimeout time.Duration
type DialTimeout time.DurationIn di.go:
dig.Provide(func() UseMySQL { return UseMySQL(true) }),
dig.Provide(func() UseRedis { return UseRedis(false) }),
dig.Provide(func() QueryTimeout { return QueryTimeout(5 * time.Second) }),
dig.Provide(func() DialTimeout { return DialTimeout(2 * time.Second) }),The generator sees each wrapper type as unique, so there is no conflict.
When you write an inline anonymous function inside dig.Provide, you must not capture variables from the outer scope of InitApp().
✅ Correct – uses only constant literals:
dig.Provide(func() QueryTimeout { return QueryTimeout(5 * time.Second) })❌ Wrong – captures a local variable t:
t := 5 * time.Second
dig.Provide(func() QueryTimeout { return QueryTimeout(t) })
// generator error: cannot capture local variable "t"Why?
The generator extracts the closure body and lifts it into a top‑level function in di_gen.go. This new function is defined at package level – it does not have access to InitApp()'s stack frame. If the closure captures a local variable from InitApp(), that variable does not exist in the package scope, causing an "undefined symbol" compile error.
Important: Even if the captured value is a constant, it is still bound to InitApp's scope. Only constant literals (like true, 3, "hello") and package‑level variables/constants are allowed, because they are resolvable at package level.
If you need a value that is computed at runtime, define it as a package‑level variable and use dig.Supply to inject it.
Because dig is a code generator, not a runtime framework, it performs static analysis on your di.go and module files. This means the generator reads your code as text, but does not execute it. Therefore, conditional logic works differently depending on where you place it.
The body of a closure passed to dig.Provide or dig.Invoke is copied as-is into the generated code. All conditional logic inside it will execute at runtime as expected.
Example (classic conditional injection):
dig.Provide(func(t QueryTimeout, useMySQL UseMySQL, cache EnableCache) Store {
if useMySQL {
return NewMySQLStore(t)
}
return NewRedisStore(cache)
})The generator copies the entire closure body into a generated function like __p_xxx. At runtime, useMySQL determines which store is created.
This is the recommended way to handle conditional logic.
The Module() function is parsed statically. The generator does not execute any if statements, loops, or branches inside it. All dig.Provide, dig.Invoke, and dig.Supply calls are extracted regardless of which branch they appear in.
Example of what does NOT work as expected:
func Module() dig.Option {
if enableCache {
return dig.Module(
dig.Provide(NewCache),
dig.Invoke(StartCache),
)
}
return dig.Module(
dig.Provide(NewNoop),
dig.Invoke(StartNoop),
)
}The generator will parse both branches and register all providers (NewCache, NewNoop, StartCache, StartNoop) into the dependency graph, regardless of enableCache. The condition is never evaluated during generation.
To achieve conditional module inclusion, use build tags instead:
// module_cache.go
//go:build enable_cache
package mod
func Module() dig.Option { return dig.Module(dig.Provide(NewCache), dig.Invoke(StartCache)) }
// module_noop.go
//go:build !enable_cache
package mod
func Module() dig.Option { return dig.Module(dig.Provide(NewNoop), dig.Invoke(StartNoop)) }Then in di.go:
func InitApp() func(context.Context) error {
return dig.Build(
mod.Module(), // build tags decide which file is compiled
)
}Same as Provide closures — the entire body is copied and runs at runtime.
dig.Invoke(func(config Config) {
if config.Debug {
log.Println("debug mode enabled")
}
})You may attempt to use an immediately-invoked function expression (IIFE) inside dig.Module:
dig.Module(
func() dig.Option {
if someCondition {
return dig.Provide(NewCache)
}
return dig.Provide(NewNoop)
}(),
)This does NOT work because someCondition cannot be evaluated at generation time. The generator will parse both branches and register both providers. Avoid this pattern.
| Location | Conditional Logic Works? | Why |
|---|---|---|
Inside Provide/Invoke closure body |
✅ Yes | Body is copied and runs at runtime |
Inside Module() function body |
❌ No | Generator does not execute control flow |
Inside dig.Module arguments via IIFE |
❌ No | Condition cannot be evaluated at generation time |
| Using build tags | ✅ Yes | Compile-time selection controlled by Go |
Rule of thumb:
- Put runtime decisions inside
Provide/Invokeclosures. - Use build tags for compile-time module selection.
- Keep
Module()functions pure — onlydig.Modulecalls andreturn.
Enable debug logging with the -debug flag.
Run the generator directly:
digen -debug -out di_gen.goOr add -debug to the //go:generate directive in di.go:
//go:generate go run -mod=mod github.com/shanjunmei/dig/cmd/digen -debug -out di_gen.goWhen enabled, the generated code will insert Logf calls like this:
Logf("[PROVIDE] before: %s\n", "main.NewConfig")
v0, err := NewConfig()
if err != nil {
Logf("[PROVIDE] failed: %s: %v\n", "main.NewConfig", err)
panic(err)
}
Logf("[PROVIDE] after: %s\n", "main.NewConfig")You'll see runtime output such as:
[PROVIDE] before: main.NewConfig
[PROVIDE] after: main.NewConfig
[PROVIDE] before: main.NewDB
[PROVIDE] after: main.NewDB
[INVOKE] before: main.(*Server).Run
[INVOKE] after: main.(*Server).Run
Override Logf at runtime – The generated file declares:
var Logf = log.PrintfYou can override this in your main.go before calling InitApp():
package main
import (
"log"
"os"
)
func main() {
customLogger := log.New(os.Stdout, "[MYAPP] ", log.LstdFlags|log.Lshortfile)
Logf = customLogger.Printf
run := InitApp()
if err := run(context.Background()); err != nil {
customLogger.Fatalf("app failed: %v", err)
}
}No external dependency – Logf uses standard log by default.
error(default) – generation fails if unused providers exist.ignore– keep unused providers with_ = fn().drop– remove unused providers entirely.
digen -unused=drop -out di_gen.gofull(default) –addr_handler,user_handlershort–handler,handler2obfuscated–a,b,c1
For larger projects, each module defines its own Module() function that returns a dig.Option. Modules can be nested – a module can include other modules, allowing hierarchical organisation.
Project structure:
myapp/
|-- di.go # top-level composition
|-- main.go
|-- internal/
| |-- db/
| | `-- module.go # db.Module()
| |-- server/
| | `-- module.go # server.Module() – may include db.Module()
| |-- logger/
| | `-- module.go # logger.Module()
| `-- monitoring/
| `-- module.go # monitoring.Module() – may include logger.Module()
`-- pkg/
`-- common/
`-- timeout.go
Example: nested modules
internal/db/module.go:
package db
import "github.com/shanjunmei/dig"
func Module() dig.Option {
return dig.Module(
dig.Provide(NewConfig),
dig.Provide(NewConnection),
dig.Invoke(func(db *DB) error { return db.Ping() }),
)
}internal/logger/module.go:
package logger
import "github.com/shanjunmei/dig"
func Module() dig.Option {
return dig.Module(
dig.Provide(New),
dig.Invoke(Init),
)
}internal/monitoring/module.go (nested module):
package monitoring
import (
"myapp/internal/logger"
"github.com/shanjunmei/dig"
)
func Module() dig.Option {
return dig.Module(
logger.Module(),
dig.Provide(NewMetricsCollector),
dig.Provide(NewHealthChecker),
dig.Invoke(StartMetricsServer),
)
}internal/server/module.go (nested module):
package server
import (
"myapp/internal/db"
"myapp/internal/monitoring"
"github.com/shanjunmei/dig"
)
func Module() dig.Option {
return dig.Module(
db.Module(),
monitoring.Module(),
dig.Provide(New),
dig.Provide(NewRouter),
dig.Invoke(RegisterRoutes),
)
}di.go – top‑level composition (mixing modules with plain providers):
//go:build digen
package main
import (
"myapp/internal/server"
"myapp/internal/logger"
"myapp/pkg/common"
"github.com/shanjunmei/dig"
)
func InitApp() func(context.Context) error {
return dig.Build(
server.Module(),
dig.Supply(common.DefaultTimeout),
dig.Provide(NewGlobalService),
logger.Module(), // safe if not already included transitively
dig.Invoke(StartGlobalWorker),
)
}Nesting benefits:
- Encapsulates complex dependency hierarchies within modules.
- Modules can be reused and composed independently.
- The top‑level
di.gostays clean even as the project grows.
Important: Do not include the same module twice (directly or transitively) – the generator will report a duplicate provider error. Design your module hierarchy so each module is included only once.
| Flag | Default | Description |
|---|---|---|
-out |
di_gen.go |
Output filename |
-unused |
error |
Behaviour for unused providers: error, ignore, drop |
-debug |
false |
Enable debug logs in generated code (uses Logf) |
-alias |
full |
Package alias strategy: short, full, or obfuscated |
Note: When
digenis invoked with./...(multi‑package mode), the-outflag is ignored anddig_gen.gois used in each package directory.
In v1.0.4 and earlier, InitApp() returned *dig.App with a Run method:
app := InitApp()
if err := app.Run(context.Background()); err != nil {
log.Fatal(err)
}In v1.0.5, InitApp() returns func(context.Context) error directly:
run := InitApp()
if err := run(context.Background()); err != nil {
log.Fatal(err)
}Migration steps:
- Change
app.Run(ctx)torun(ctx)whererun := InitApp() - Remove any references to the
dig.Apptype in your code - Update your
di.gosignature fromfunc InitApp() *dig.Apptofunc InitApp() func(context.Context) error - Run
go generateto regeneratedi_gen.go - Run
go mod tidyto update dependencies
Why this change? – Starting from v1.0.5, the generated code no longer imports the dig package at runtime. This eliminates the runtime dependency entirely, resulting in smaller binaries and zero reflection overhead.
| Feature | dig | Google Wire | Uber dig / FX |
|---|---|---|---|
| Approach | Code generation (compile‑time) | Code generation (compile‑time) | Runtime reflection (no generation) |
| Code generation workflow | ✅ digen CLI |
✅ wire CLI |
❌ Not applicable |
| Zero runtime reflection | ✅ | ✅ | ❌ |
| Zero runtime dependency | ✅ | ✅ | ❌ |
| Dependency validation | At generation time | At generation time | At runtime |
Dedicated Supply API |
✅ | ❌ | ❌ |
| Closure safety enforcement | ✅ (capture check) | N/A | |
| Wrapper type support | ✅ | ❌ | |
Built‑in Invoke |
✅ | ❌ | ✅ (lifecycle hooks) |
dig.Module composition |
✅ (with nesting) | ❌ | ✅ (fx.Module) |
| Unused provider policies | 3 modes | only drop |
N/A |
| Built‑in debug logging | ✅ (with runtime override) | ✅ (tracing) | |
| External dependencies | none (std only) | none | many |
| Generated code size | Compact | Verbose | N/A |
| Generation performance | Fast (AST rewrite) | Slower (full type‑checking) | N/A |
| Learning curve | Low | Medium | High |
MIT – see LICENSE for details.