Skip to content
Vincent edited this page Jul 18, 2026 · 4 revisions

dig Wiki

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.


📖 Table of Contents


✨ Core Features

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.

⚖️ Comparison with Wire / Fx

Overview

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 ⚠️ flat composition ✅ explicit with names
Generic support ✅ compile-time native ⚠️ cumbersome ✅ 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

Multi-Instance Capabilities – Deep Dive

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: ...})

dig vs Wire – Advantages

  • No need to repeatedly combine wire.NewSet – just use dig.Module or flatten.
  • dig.Supply works with any expression, not just compile-time constants.
  • No wire.Bind for interface binding – constructors can directly return interface types.
  • No dummy return nil, nil placeholder after wire.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.

dig vs Fx – Advantages

  • 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.Module is purely for code organization, no name strings.
  • More intuitive multi-instance injection – dig uses parameter names; Fx requires group tags.

📦 Installation

go get github.com/shanjunmei/dig@v1.0.11
go install github.com/shanjunmei/dig/cmd/digen@latest

Requires Go 1.21+.


🚀 Minimal Example

1. Create di.go (assembly)

//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() }),
    )
}

2. Create main.go (business logic)

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)
    }
}

3. Generate and run

digen ./...   # or go generate ./...
go run .

📚 Core API

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.

Important Constraints

  1. Closure capture restriction: Closures in Provide/Invoke must not capture local variables from InitApp. Only package-level symbols and literals are allowed.
  2. External parameters are auto-supplied: Parameters of InitApp become available in the container automatically.
  3. Generics must be instantiated explicitly: e.g., dig.Provide(NewStore[int]).
  4. Type conflicts: Use distinct types or named instances to disambiguate multiple values of the same underlying type.

🏷️ Named Instance Injection

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

How It Works

  1. Define a provider with named return values – the names become the “instance names”.
  2. Depend on a specific instance by using the same parameter name in your consumer function.

Example

// 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
})

Using dig.Supply with Names

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.

Error Handling

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

Compatibility

  • Existing code that uses a single instance of a type remains unchanged.
  • The feature is additive – no breaking changes.

🧩 Modular Example

Project Structure

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

Sub-module (user/repository/module.go)

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() }),
    )
}

Top-level Module (user/module.go)

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]),
    )
}

Application Assembly (app/app.go)

//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
        }),
    )
}

🔄 Migration from Wire

Typical Wire code

//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
}

After migration to dig

//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-by-step migration

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 ./...

🔄 Migration from Fx

Typical Fx code

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()
}

After migration to dig

// 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-by-step migration

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

❓ FAQ

Q: Why can't closures capture local variables from InitApp?

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).

Q: How to debug dependency resolution?

Run digen -debug. The generated code will include Logf calls. At runtime, override Logf to see the resolution steps.

Q: Can I commit the generated code?

Yes. di_gen.go is plain Go with no external imports. You can commit it, and others can build without installing dig.

Q: How to handle multiple values of the same type?

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.

Q: What is the difference between dig and uber-go/dig?

shanjunmei/dig is a compile-time generator producing code with zero runtime dependency. uber-go/dig is a runtime reflection container.

Q: Does dig support multiple instances like Fx's value groups?

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.

Q: Why does wire not support multiple instances of the same type?

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.


📎 Links