Files
teamai-test/.teamai/skills/common/golang-google-wire/references/advanced.md
T

6.8 KiB

Advanced — google/wire

Detail topics referenced from SKILL.md. Each section is self-contained.

Cleanup Chains

When a provider returns (T, func(), error), Wire adds the cleanup to a chain. The generated injector runs cleanups in reverse construction order: the last-built dependant is cleaned up first, ensuring dependants are torn down before their dependencies.

// Provider with cleanup
func NewDB(cfg *Config) (*sql.DB, func(), error) {
    db, err := sql.Open("postgres", string(cfg.DSN))
    if err != nil { return nil, nil, err }
    return db, func() { db.Close() }, nil
}

func NewCache(cfg *Config) (*redis.Client, func(), error) {
    c := redis.NewClient(&redis.Options{Addr: cfg.CacheAddr})
    return c, func() { c.Close() }, nil
}

Wire generates something like:

func InitApp() (*App, func(), error) {
    cfg := NewConfig()
    db, dbCleanup, err := NewDB(cfg)
    if err != nil { return nil, nil, err }
    cache, cacheCleanup, err := NewCache(cfg)
    if err != nil {
        dbCleanup()  // already-built cleanups run on partial failure
        return nil, nil, err
    }
    app := NewApp(db, cache)
    return app, func() {
        cacheCleanup()  // reverse order
        dbCleanup()
    }, nil
}

Caller pattern — guard against nil cleanup on construction failure:

app, cleanup, err := InitApp()
if err != nil { log.Fatal(err) }
defer cleanup()

Wire always returns a non-nil cleanup function when construction succeeds. If construction fails midway, the returned cleanup is nil — guard before calling.

Multiple Injectors in One Package

A package can contain multiple injector functions. Each must live in a file with //go:build wireinject. All generated functions land in wire_gen.go in the same package.

//go:build wireinject

package main

// Production injector
func InitProdApp() (*App, func(), error) {
    wire.Build(ProdSet, NewApp)
    return nil, nil, nil
}

// Development injector with debug providers
func InitDevApp() (*App, func(), error) {
    wire.Build(DevSet, NewApp)
    return nil, nil, nil
}

Select at runtime with a flag, or at build time with separate //go:build prod / //go:build !prod constraints on the injector files.

wire.NewSet Nesting Strategies

Sets can contain other sets, building a hierarchy that mirrors your package structure.

// pkg/config/wire.go
var ConfigSet = wire.NewSet(NewConfig)

// pkg/infra/wire.go
var InfraSet = wire.NewSet(
    config.ConfigSet, // embed upstream set
    NewDB,
    NewCache,
)

// pkg/service/wire.go
var ServiceSet = wire.NewSet(
    NewUserService,
    wire.Bind(new(UserStore), new(*UserRepo)),
)

// wire.go (injector)
wire.Build(infra.InfraSet, service.ServiceSet, NewApp)

Library set stability rules (from upstream best practices):

  • Safe: replace one provider with another that has the same or fewer inputs, in the same release.
  • Safe: introduce a brand-new output type not previously provided.
  • Breaking: add a new required input to a provider — downstream injectors cannot satisfy it.
  • Breaking: remove a provided output type — downstream injectors that depend on it fail.
  • Breaking: add a type that the injector already provides — Wire reports a duplicate.

wire:"-" Exclusion Tag

Exclude a struct field from wire.Struct injection by tagging it:

type Server struct {
    Logger  *zap.Logger
    DB      *sql.DB
    mu      sync.Mutex   `wire:"-"` // unexported — auto-excluded
    Timeout time.Duration `wire:"-"` // exported but opt-out
}

wire.Struct(new(Server), "*") // injects Logger and DB; skips mu and Timeout

Unexported fields are always skipped regardless of the tag.

Common Codegen Errors

Error message Root cause Fix
no provider found for TYPE A dependency is not provided by any set in wire.Build Add the missing provider or set
multiple bindings for TYPE Two providers return the same type Use named types or remove the duplicate
argument N has no provider for TYPE An interface is requested but no wire.Bind maps to it Add wire.Bind(new(Iface), new(*Impl)) to a set
cycle detected A → B → A circular dependency Break the cycle by introducing an interface or factory
wire.Build used outside of injector function wire.Build called from a non-injector function Only call wire.Build inside functions with the build tag
duplicate symbol / redeclared in this block Injector file is missing //go:build wireinject Add the build tag as the first line

Codegen Flags

# Specify output file prefix (default: wire_gen)
wire -output_file_prefix=init gen ./cmd/server

# Apply build tags during generation
wire -tags=integration gen ./...

# Prepend a header file (e.g., license comment) to generated output
wire -header_file=hack/boilerplate.go.txt gen ./...

panic(wire.Build(...)) Alternate Syntax

Wire accepts either a dummy return or a panic call as the injector body. The panic form avoids writing zero-value returns for complex types:

// Preferred when return types are complex or error-prone to zero-initialize
func InitApp(ctx context.Context) (*App, func(), error) {
    panic(wire.Build(AppSet))
}

Wire detects both forms and replaces the body during codegen. The panic is never reached in the compiled binary — only the generated wire_gen.go version is compiled.

Accepting External Values as Injector Arguments

When a value is constructed before wire runs (e.g., parsed flags, an http.Client from a test), pass it as a parameter to the injector rather than providing it from within the graph:

//go:build wireinject

func InitApp(cfg *Config) (*App, func(), error) {
    wire.Build(InfraSet, ServiceSet, NewApp)
    return nil, nil, nil
}

// main.go
cfg := parseFlags()
app, cleanup, err := InitApp(cfg)

Wire treats injector parameters as pre-built providers — they satisfy dependencies without needing a wire.NewSet entry.

Quick Reference

Symbol Purpose
wire.NewSet(providers...) Group providers into a reusable set
wire.Build(sets...) Declare injector body (codegen replaces it)
wire.Bind(new(Iface), new(*Concrete)) Bind interface to concrete type
wire.Struct(new(T), "Field", ...) Inject struct fields from the graph
wire.Struct(new(T), "*") Inject all non-excluded fields
wire.Value(expr) Bind a constant expression (no fn calls/channels)
wire.InterfaceValue(new(I), value) Bind a value to an interface type
wire.FieldsOf(new(T), "Field", ...) Promote struct fields as individual graph nodes
//go:build wireinject Build tag: exclude injector stub from binary
wire_gen.go Generated output — commit, never edit
wire ./... Regenerate all injectors in the module
wire check ./... Validate graph without regenerating