返回 Skills
samber/cc-skills-golang· MIT 内容可用

golang-google-wire

Compile-time dependency injection in Golang using google/wire — wire.NewSet, wire.Build, wire.Bind (interface→concrete), wire.Struct, wire.Value, wire.InterfaceValue, wire.FieldsOf, cleanup functions, //go:build wireinject injector files, and generated wire_gen.go. Apply when using or adopting google/wire, when the codebase imports `github.com/google/wire`, or when wiring an application graph at compile time via `wire.Build`. For runtime DI with reflection, see `samber/cc-skills-golang@golang-uber-dig` skill.

安装

与 skills.sh 相同的 Command / Prompt 安装方式


name: golang-google-wire description: "Compile-time dependency injection in Golang using google/wire — wire.NewSet, wire.Build, wire.Bind (interface→concrete), wire.Struct, wire.Value, wire.InterfaceValue, wire.FieldsOf, cleanup functions, //go:build wireinject injector files, and generated wire_gen.go. Apply when using or adopting google/wire, when the codebase imports github.com/google/wire, or when wiring an application graph at compile time via wire.Build. For runtime DI with reflection, see samber/cc-skills-golang@golang-uber-dig skill." user-invocable: true license: MIT compatibility: Designed for Claude Code or similar AI coding agents, and for projects using Golang. metadata: author: samber version: "1.0.6" openclaw: emoji: "🪡" homepage: https://github.com/samber/cc-skills-golang requires: bins: - go - wire install: - kind: go package: github.com/google/wire/cmd/wire@latest bins: [wire] skill-library-version: "0.7.0" allowed-tools: Read Edit Write Glob Grep Bash(go:) Bash(golangci-lint:) Bash(git:) Agent WebFetch mcp__context7__resolve-library-id mcp__context7__query-docs Bash(wire:) Bash(godig:) Bash(gopls:) LSP mcp__gopls__*

Persona: You are a Go architect using wire for compile-time DI. You let the compiler catch missing dependencies, treat wire_gen.go as committed source, and re-run wire ./... after every graph change.

Dependencies:

  • wire: go install github.com/google/wire/cmd/wire@latest

Using google/wire for Compile-Time Dependency Injection in Go

Code-generation DI toolkit. Wire resolves the dependency graph at compile time and emits plain Go constructor calls — no runtime container, no reflection. Errors appear when you run wire ./..., not at first request.

Note: google/wire was archived in August 2025 (feature-complete; bug fixes still accepted).

Official Resources: pkg.go.dev · github.com/google/wire · User Guide · Best Practices

This skill is not exhaustive. Please refer to library documentation and code examples for more information. For Go package docs, symbols, versions, importers, and known vulnerabilities, → See samber/cc-skills-golang@golang-pkg-go-dev skill (godig) — prefer it over Context7 for Go package facts. To navigate this library's usage in your own code (definitions, call sites, diagnostics), → See samber/cc-skills-golang@golang-gopls skill (gopls). Context7 remains a fallback for docs not indexed on pkg.go.dev.

go get -tool github.com/google/wire/cmd/wire@latest
go get github.com/google/wire

wire vs. Runtime DI

Concernwiredig / fx / samber/do
ResolutionCompile time (codegen)Runtime (reflection)
Error detectionwire ./... failsFirst Invoke/startup
Runtime containerNone — plain Go callsPresent
Lifecycle hooksNot built infx: OnStart/OnStop
Generated fileswire_gen.go (committed)None

For lifecycle, lazy loading, and a full matrix see samber/cc-skills-golang@golang-dependency-injection.

Providers

A provider is any Go function — inputs are dependencies, outputs are provided types. Three return forms:

func NewConfig() *Config                          { return &Config{Addr: ":8080"} }
func NewDB(cfg *Config) (*sql.DB, error)          { return sql.Open("postgres", cfg.DSN) }
func NewRedis(cfg *Config) (*redis.Client, func(), error) { // cleanup chained in reverse order
    c := redis.NewClient(&redis.Options{Addr: cfg.RedisAddr})
    return c, func() { c.Close() }, nil
}

Provider Sets

wire.NewSet groups providers for reuse. Sets can reference other sets.

// infra/wire.go
var InfraSet = wire.NewSet(
    NewConfig,
    NewDB,
    NewRedis,
)

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

Keep sets small: library sets expose a stable surface (adding inputs or removing outputs breaks downstream injectors). One set per package is a useful default.

Injectors and //go:build wireinject

The injector file declares the initialization function. Wire generates its body into wire_gen.go and replaces the stub.

//go:build wireinject

package main

import "github.com/google/wire"

// Wire generates the body of this function.
func InitApp() (*App, func(), error) {
    wire.Build(InfraSet, ServiceSet, NewApp)
    return nil, nil, nil // replaced by codegen
}

The //go:build wireinject tag prevents the stub from being compiled into the binary — only wire_gen.go (which has no such tag) makes it through go build. Without this tag, both files define the same function, causing a compile error.

Alternative syntax when a dummy return is inconvenient:

func InitApp() (*App, func(), error) {
    panic(wire.Build(InfraSet, ServiceSet, NewApp))
}

Interface Bindings

Wire forbids implicit interface satisfaction — you must declare bindings explicitly so the graph is unambiguous when multiple types implement the same interface.

var Set = wire.NewSet(
    NewPostgresUserRepo,
    wire.Bind(new(UserStore), new(*PostgresUserRepo)), // tell wire: *PostgresUserRepo satisfies UserStore
)

Explicit bindings prevent graph breakage when a new type implementing the same interface is added elsewhere.

Struct Providers and Values

wire.Struct fills struct fields from the graph without a manual constructor. Tag fields wire:"-" to exclude them.

wire.Struct(new(Server), "Logger", "DB") // inject named fields
wire.Struct(new(Server), "*")            // inject all non-excluded fields
wire.Value(Foo{X: 42})                   // constant expression (no fn calls / channels)
wire.InterfaceValue(new(io.Reader), os.Stdin) // interface-typed literal
wire.FieldsOf(new(Config), "DSN", "Addr")    // promote struct fields as graph nodes

See advanced.md for the wire:"-" exclusion tag and wire.FieldsOf details.

Disambiguating Duplicate Types

Wire forbids two providers for the same type. Wrap the underlying type in distinct named types so each has exactly one provider:

type PrimaryDSN string
type ReplicaDSN string

Full Application Example

// wire.go — injector, excluded from binary via build tag
//go:build wireinject

package main

func InitApp() (*App, func(), error) {
    wire.Build(config.ConfigSet, infra.InfraSet, service.ServiceSet, NewApp)
    return nil, nil, nil
}

// main.go
func main() {
    app, cleanup, err := InitApp()
    if err != nil { log.Fatal(err) }
    defer cleanup()
    app.Run()
}

Wire generates wire_gen.go (plain Go, committed, DO NOT EDIT). For a full example with per-package sets, cleanup-heavy graphs, and generated output, see recipes.md.

Codegen Workflow

wire ./...           # regenerate all injectors in the module
wire check ./...     # validate graph without regenerating (fast CI check)

Run wire ./... after every constructor signature change. Add //go:generate go run github.com/google/wire/cmd/wire to injector files so go generate ./... also works. Commit wire_gen.go — it must stay in sync for CI builds.

Best Practices

  1. Never edit wire_gen.go — it is overwritten on every wire ./... run. Treat it as a build artifact that happens to be committed; source of truth is the provider and injector files.
  2. Always add //go:build wireinject to injector files — omitting it causes duplicate-symbol compile errors because both the stub and the generated file define the same function.
  3. Use named types to distinguish values of the same underlying type — wire enforces one provider per type; named types like type DSN string let you have PrimaryDSN and ReplicaDSN coexist.
  4. Keep library provider sets minimal and backward-compatible — adding new required inputs breaks downstream injectors; removing outputs does too. Introduce only newly-created types in the same release.
  5. Return (T, func(), error) from cleanup providers and let wire chain them — wire generates the correct reverse-order cleanup and handles partial failures (if construction fails midway, only already-built cleanups run).
  6. Keep injector files focused — one function per file, one package import at a time. Fat injectors with dozens of wire.Build arguments are hard to reason about; delegate to per-package sets.

Common Mistakes

MistakeFix
Editing wire_gen.go manuallyNever edit it. Change providers or injectors and re-run wire ./....
Missing //go:build wireinjectAdd the tag as the very first line of every injector file.
Two providers returning *sql.DBWrap with a named struct type: type PrimaryDB struct { *sql.DB } — Wire does not distinguish pointer type aliases.
Injecting an interface without wire.BindAdd wire.Bind(new(MyInterface), new(*MyImpl)) to the provider set.
Forgetting to re-run wire ./... after changesRun wire before go build; add it to go generate or a Makefile target.
Calling cleanup() without guarding for nilWire returns nil cleanup on construction error; guard with if cleanup != nil { defer cleanup() }.

Testing

Wire generates plain Go constructors, so unit tests use manual injection — no container to clone or reset. For testing patterns (test injectors swapping real providers for fakes, CI stale-check for wire_gen.go), see testing.md.

Further Reading

  • advanced.md — cleanup chains, multiple injectors, set nesting, error catalogue, codegen flags, quick reference
  • recipes.md — HTTP server, multi-injector build, cleanup-heavy graph, CLI embedding
  • testing.md — test injectors, fake bindings, CI stale check

Cross-References

  • → See samber/cc-skills-golang@golang-dependency-injection skill for DI concepts and library comparison
  • → See samber/cc-skills-golang@golang-uber-dig skill for runtime reflection-based DI without lifecycle
  • → See samber/cc-skills-golang@golang-uber-fx skill for runtime DI with lifecycle hooks, modules, and signal-aware Run()
  • → See samber/cc-skills-golang@golang-samber-do skill for generics-based DI without reflection
  • → See samber/cc-skills-golang@golang-structs-interfaces skill for interface design patterns
  • → See samber/cc-skills-golang@golang-testing skill for general testing patterns

If you encounter a bug or unexpected behavior in google/wire, open an issue at https://github.com/google/wire/issues.

附带文件

evals/evals.json
[
  {
    "id": 1,
    "name": "build-constraint-on-injector",
    "description": "Tests that the model adds //go:build wireinject to injector files to prevent duplicate-symbol compile errors",
    "prompt": "I'm setting up google/wire in my Go project. Here's my injector file:\n\n```go\npackage main\n\nimport \"github.com/google/wire\"\n\nfunc InitApp() (*App, error) {\n    wire.Build(InfraSet, ServiceSet, NewApp)\n    return nil, nil\n}\n```\n\nWire generates wire_gen.go successfully, but when I run `go build`, I get a 'redeclared in this block' compile error for InitApp. What's wrong and how do I fix it?",
    "trap": "Without the skill, the model may suggest renaming the function, reorganizing packages, or not identify that the missing //go:build wireinject tag is the cause — both the stub and wire_gen.go define InitApp, causing the duplicate.",
    "assertions": [
      {"id": "1.1", "text": "Identifies the missing //go:build wireinject build tag as the root cause"},
      {"id": "1.2", "text": "Shows //go:build wireinject as the first line of the injector file"},
      {"id": "1.3", "text": "Explains that the tag prevents the stub from being compiled into the binary (only wire_gen.go compiles)"},
      {"id": "1.4", "text": "Does NOT suggest renaming the function or reorganizing packages as the fix"},
      {"id": "1.5", "text": "Does NOT suggest deleting wire_gen.go as the fix"}
    ]
  },
  {
    "id": 2,
    "name": "interface-binding-required",
    "description": "Tests that wire.Bind is required for interface-to-concrete mappings and cannot be inferred",
    "prompt": "I have this Go code using google/wire:\n\n```go\n// repo.go\ntype UserStore interface {\n    GetUser(id int64) (*User, error)\n}\n\ntype PostgresUserRepo struct{ db *sql.DB }\nfunc (r *PostgresUserRepo) GetUser(id int64) (*User, error) { ... }\nfunc NewUserRepo(db *sql.DB) *PostgresUserRepo { return &PostgresUserRepo{db: db} }\n\n// service.go\nfunc NewUserService(store UserStore) *UserService { return &UserService{store: store} }\n\n// wire_providers.go\nvar AppSet = wire.NewSet(NewDB, NewUserRepo, NewUserService)\n```\n\nWhen I run `wire ./...` I get: `no provider found for UserStore`. NewUserRepo returns *PostgresUserRepo which clearly implements UserStore. Why doesn't wire figure this out?",
    "trap": "Without the skill, the model might suggest wire should automatically resolve the interface, or suggest wrapping NewUserRepo to return UserStore directly, missing the explicit wire.Bind requirement.",
    "assertions": [
      {"id": "2.1", "text": "Explains that wire never auto-resolves interface satisfaction — bindings must be explicit"},
      {"id": "2.2", "text": "Shows wire.Bind(new(UserStore), new(*PostgresUserRepo)) added to the provider set"},
      {"id": "2.3", "text": "Places wire.Bind inside the same wire.NewSet (or adds it to a set in wire.Build)"},
      {"id": "2.4", "text": "Explains WHY wire requires explicit bindings (predictability — avoids surprise rebinding when new implementations are added)"},
      {"id": "2.5", "text": "Does NOT suggest changing NewUserRepo to return UserStore directly as the primary fix"}
    ]
  },
  {
    "id": 3,
    "name": "duplicate-type-named-wrapper",
    "description": "Tests the named-type pattern to disambiguate multiple values of the same underlying type",
    "prompt": "I'm building a Go service with google/wire. I need to inject two database connection strings — one for the primary database and one for a read replica. I tried this:\n\n```go\nfunc NewPrimaryDSN() string { return os.Getenv(\"PRIMARY_DSN\") }\nfunc NewReplicaDSN() string { return os.Getenv(\"REPLICA_DSN\") }\n\nvar DBSet = wire.NewSet(NewPrimaryDSN, NewReplicaDSN, NewPrimaryDB, NewReplicaDB)\n```\n\nWire complains about multiple bindings for string. How should I structure this?",
    "trap": "Without the skill, the model might suggest using wire.Value or provider arguments, or use a config struct — missing the idiomatic named-type wrapper pattern that wire's own docs recommend.",
    "assertions": [
      {"id": "3.1", "text": "Introduces distinct named types (e.g., type PrimaryDSN string and type ReplicaDSN string)"},
      {"id": "3.2", "text": "Updates NewPrimaryDSN to return PrimaryDSN and NewReplicaDSN to return ReplicaDSN"},
      {"id": "3.3", "text": "Updates NewPrimaryDB and NewReplicaDB signatures to accept the named types"},
      {"id": "3.4", "text": "Explains that wire enforces one provider per type, so distinct named types are the correct solution"},
      {"id": "3.5", "text": "Does NOT suggest using a single Config struct with both DSNs as the primary fix (that avoids the problem rather than solving it with named types)"}
    ]
  },
  {
    "id": 4,
    "name": "cleanup-signature",
    "description": "Tests the (T, func(), error) cleanup provider pattern instead of manual defer in main",
    "prompt": "I'm using google/wire to wire my Go service. I need my *sql.DB connection pool to be closed when the app shuts down. Currently I'm doing this in main:\n\n```go\nfunc main() {\n    app, err := InitApp()\n    if err != nil { log.Fatal(err) }\n    defer db.Close() // but I don't have access to db here!\n    app.Run()\n}\n```\n\nI realize I need the DB closed on shutdown, but InitApp() only returns *App. How should I wire cleanup with google/wire?",
    "trap": "Without the skill, the model might suggest passing db out of InitApp as a second return value, or storing it as a global, missing the (T, func(), error) cleanup provider pattern.",
    "assertions": [
      {"id": "4.1", "text": "Changes NewDB to return (*sql.DB, func(), error) where the cleanup function calls db.Close()"},
      {"id": "4.2", "text": "Changes the injector function to return (*App, func(), error) to propagate the cleanup chain"},
      {"id": "4.3", "text": "Shows main calling defer cleanup() after the nil-check"},
      {"id": "4.4", "text": "Explains that wire chains cleanup functions and calls them in reverse construction order"},
      {"id": "4.5", "text": "Does NOT suggest passing db as an extra return value from InitApp alongside *App"}
    ]
  },
  {
    "id": 5,
    "name": "no-edit-wire-gen",
    "description": "Tests that the model never edits wire_gen.go and instructs re-running wire ./... instead",
    "prompt": "I added a new *Logger parameter to my NewServer constructor in my google/wire project:\n\n```go\nfunc NewServer(db *sql.DB, log *zap.Logger) *Server { ... }\n```\n\nNow `go build` fails with 'too few arguments in call to NewServer'. The error is inside wire_gen.go on line 47. Should I edit wire_gen.go to add the logger argument there, or is there another way?",
    "trap": "Without the skill, a model may suggest editing wire_gen.go directly to 'fix' the build error quickly, which would be overwritten on the next wire run.",
    "assertions": [
      {"id": "5.1", "text": "Explicitly says NOT to edit wire_gen.go (it is always overwritten)"},
      {"id": "5.2", "text": "Instructs running wire ./... to regenerate wire_gen.go"},
      {"id": "5.3", "text": "Explains that *zap.Logger must be provided in the graph (either via a provider or wire.Value)"},
      {"id": "5.4", "text": "Shows how to add NewLogger (or wire.Value) to the appropriate wire.NewSet so the dependency is satisfied"},
      {"id": "5.5", "text": "Does NOT present editing wire_gen.go as an option"}
    ]
  },
  {
    "id": 6,
    "name": "provider-set-organization",
    "description": "Tests per-package provider set organization instead of one giant set in main",
    "prompt": "My Go service using google/wire is growing. I currently have everything in one place:\n\n```go\n// wire.go\n//go:build wireinject\n\nfunc InitApp() (*App, func(), error) {\n    wire.Build(\n        NewConfig, NewDB, NewCache, NewLogger,\n        NewUserRepo, NewOrderRepo, NewProductRepo,\n        wire.Bind(new(UserStore), new(*PostgresUserRepo)),\n        wire.Bind(new(OrderStore), new(*PostgresOrderRepo)),\n        wire.Bind(new(ProductStore), new(*PostgresProductRepo)),\n        NewUserService, NewOrderService, NewProductService,\n        NewHTTPServer, NewRouter,\n        NewApp,\n    )\n    return nil, nil, nil\n}\n```\n\nThis is getting unwieldy. How should I organize this with google/wire?",
    "trap": "Without the skill, the model may just split the providers into helper variables in the same package, missing the idiomatic per-package wire.NewSet pattern.",
    "assertions": [
      {"id": "6.1", "text": "Introduces per-package wire.NewSet variables (e.g., InfraSet, RepoSet, ServiceSet, TransportSet)"},
      {"id": "6.2", "text": "Each set lives in its own package's wire.go file (not all in main)"},
      {"id": "6.3", "text": "The injector wire.Build references the set variables rather than individual providers"},
      {"id": "6.4", "text": "wire.Bind declarations move into the relevant package's set (not into wire.Build directly)"},
      {"id": "6.5", "text": "Explains the benefit: per-package sets are independently composable and keep the injector readable"}
    ]
  },
  {
    "id": 7,
    "name": "injector-parameter-vs-value-provider",
    "description": "Tests using wire.Value or injector parameters for pre-built values instead of wrapper constructors",
    "prompt": "In my Go app using google/wire, I parse a *Config struct from command-line flags in main() before calling InitApp. I tried writing a no-op provider:\n\n```go\nvar parsedCfg *Config\n\nfunc ProvideConfig() *Config { return parsedCfg }\n\nvar AppSet = wire.NewSet(ProvideConfig, ...)\n```\n\nThis works but feels wrong — I'm using a global variable. Is there a cleaner way to pass a pre-built *Config into the wire graph?",
    "trap": "Without the skill, the model might suggest keeping the global variable pattern or using init(), missing both wire.Value and the injector-parameter patterns.",
    "assertions": [
      {"id": "7.1", "text": "Shows the injector-parameter approach: func InitApp(cfg *Config) (*App, func(), error) with wire.Build"},
      {"id": "7.2", "text": "OR shows wire.Value(cfg) inside wire.Build — both are valid answers"},
      {"id": "7.3", "text": "Explains that injector parameters are treated as pre-built providers by wire"},
      {"id": "7.4", "text": "Does NOT use a global variable as the recommended solution"},
      {"id": "7.5", "text": "Does NOT suggest using init() to set the value"}
    ]
  },
  {
    "id": 8,
    "name": "fields-of-struct",
    "description": "Tests wire.FieldsOf to expose struct fields as individual graph nodes",
    "prompt": "I have a single Config struct in my Go app with google/wire:\n\n```go\ntype Config struct {\n    DatabaseDSN  string\n    CacheAddress string\n    APIKey       string\n}\n\nfunc NewConfig() *Config { return loadFromEnv() }\n```\n\nNewDB needs a DatabaseDSN string, NewCache needs a CacheAddress string, NewExternalClient needs an APIKey string — but all three are plain strings. How do I make these available to the wire graph without creating three separate provider functions?",
    "trap": "Without the skill, the model will suggest three named-type wrappers or three extraction functions, missing wire.FieldsOf which promotes struct fields directly.",
    "assertions": [
      {"id": "8.1", "text": "Uses wire.FieldsOf(new(Config), \"DatabaseDSN\", \"CacheAddress\", \"APIKey\") or a subset"},
      {"id": "8.2", "text": "Places wire.FieldsOf inside the provider set or wire.Build"},
      {"id": "8.3", "text": "Updates NewDB, NewCache, NewExternalClient to accept the string fields as parameters (or uses named types alongside FieldsOf)"},
      {"id": "8.4", "text": "Explains that wire.FieldsOf promotes struct fields as individual graph nodes without manual extraction functions"},
      {"id": "8.5", "text": "Does NOT suggest writing three separate func GetDatabaseDSN(c *Config) string extractor functions as the primary recommendation"}
    ]
  },
  {
    "id": 9,
    "name": "test-injector-pattern",
    "description": "Tests the test-injector pattern with wire.Bind for fake dependencies instead of runtime mocking hacks",
    "prompt": "My Go service is wired with google/wire. I have a Mailer interface implemented by SMTPMailer in production. I want integration tests that use a FakeMailer instead — recording sent emails — without modifying the production provider sets. The test must wire the full graph (not just NewUserService in isolation). How should I approach this?",
    "trap": "Without the skill, the model may suggest monkey-patching, a global variable for the mailer, or a runtime DI container for tests — missing the test-injector pattern with a test-only wire.NewSet and wire.Bind.",
    "assertions": [
      {"id": "9.1", "text": "Creates a test-only provider set (e.g., TestMailerSet) with NewFakeMailer and wire.Bind(new(Mailer), new(*FakeMailer))"},
      {"id": "9.2", "text": "Creates a test injector function in a _test.go file with //go:build wireinject"},
      {"id": "9.3", "text": "The test injector's wire.Build composes the production sets with the test-only set"},
      {"id": "9.4", "text": "Does NOT suggest global variables, monkey-patching, or a runtime DI container for tests"},
      {"id": "9.5", "text": "Mentions that wire ./... (or go generate) must be run to produce the test-injector generated code"}
    ]
  },
  {
    "id": 10,
    "name": "wire-vs-fx-for-daemon",
    "description": "Tests that the model recommends uber-go/fx over wire for long-running services that need lifecycle management",
    "prompt": "I'm starting a new Go HTTP server project and evaluating DI options. A colleague suggested google/wire because 'it's simpler and type-safe at compile time.' The server needs graceful shutdown (drain in-flight requests), OnStart/OnStop hooks for the database pool and metrics exporter, and should handle SIGINT/SIGTERM. Should I use wire?",
    "trap": "Without the skill, the model may agree that wire is suitable because it's simple and compile-time safe, not recognizing that lifecycle, signal handling, and hook ordering are exactly what fx provides and wire explicitly lacks.",
    "assertions": [
      {"id": "10.1", "text": "Identifies that wire has no built-in lifecycle management (no OnStart/OnStop hooks)"},
      {"id": "10.2", "text": "Identifies that wire has no built-in signal handling (SIGINT/SIGTERM)"},
      {"id": "10.3", "text": "Recommends uber-go/fx (or at minimum flags it as the better fit) for a long-running HTTP daemon with lifecycle needs"},
      {"id": "10.4", "text": "Does NOT recommend wire as sufficient for a service requiring graceful shutdown and lifecycle hooks"},
      {"id": "10.5", "text": "Mentions that with wire the developer must implement shutdown and signal handling manually"}
    ]
  }
]
references/advanced.md
# 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.

```go
// 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:

```go
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:

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

```go
// 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:

```go
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

```bash
# 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:

```go
// 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
//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 |
references/recipes.md
# Recipes — google/wire

End-to-end examples. Each recipe is self-contained.

## HTTP Server with Postgres and Redis

A typical service: parsed config → DB (with cleanup) → Redis (with cleanup) → repo → service → HTTP server.

```
myapp/
├── config/
│   ├── config.go
│   └── wire.go
├── infra/
│   ├── db.go
│   ├── cache.go
│   └── wire.go
├── repo/
│   ├── user.go
│   └── wire.go
├── service/
│   ├── user.go
│   └── wire.go
├── transport/
│   ├── handler.go
│   └── wire.go
├── wire.go        // injector — //go:build wireinject
├── wire_gen.go    // generated — commit this
└── main.go
```

```go
// config/config.go
type Config struct {
    Addr      string
    DSN       string
    CacheAddr string
}

func NewConfig() *Config {
    return &Config{
        Addr:      env("ADDR", ":8080"),
        DSN:       mustEnv("DATABASE_URL"),
        CacheAddr: env("REDIS_ADDR", "localhost:6379"),
    }
}

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

```go
// infra/db.go
func NewDB(cfg *config.Config) (*sql.DB, func(), error) {
    db, err := sql.Open("postgres", cfg.DSN)
    if err != nil { return nil, nil, err }
    if err := db.Ping(); err != nil { db.Close(); return nil, nil, err }
    return db, func() { db.Close() }, nil
}

// infra/cache.go
func NewRedis(cfg *config.Config) (*redis.Client, func(), error) {
    c := redis.NewClient(&redis.Options{Addr: cfg.CacheAddr})
    if err := c.Ping(context.Background()).Err(); err != nil {
        return nil, nil, err
    }
    return c, func() { c.Close() }, nil
}

// infra/wire.go
var InfraSet = wire.NewSet(NewDB, NewRedis)
```

```go
// repo/user.go
type UserStore interface {
    GetUser(ctx context.Context, id int64) (*User, error)
}

type PostgresUserRepo struct{ db *sql.DB }

func NewUserRepo(db *sql.DB) *PostgresUserRepo { return &PostgresUserRepo{db: db} }

// repo/wire.go
var RepoSet = wire.NewSet(
    NewUserRepo,
    wire.Bind(new(UserStore), new(*PostgresUserRepo)),
)
```

```go
// service/user.go
type UserService struct {
    store  repo.UserStore
    cache  *redis.Client
}

func NewUserService(store repo.UserStore, cache *redis.Client) *UserService {
    return &UserService{store: store, cache: cache}
}

// service/wire.go
var ServiceSet = wire.NewSet(NewUserService)
```

```go
// wire.go
//go:build wireinject

package main

func InitApp() (*transport.Handler, func(), error) {
    wire.Build(
        config.ConfigSet,
        infra.InfraSet,
        repo.RepoSet,
        service.ServiceSet,
        transport.NewHandler,
    )
    return nil, nil, nil
}
```

```go
// main.go
func main() {
    handler, cleanup, err := InitApp()
    if err != nil { log.Fatal(err) }
    defer cleanup()

    srv := &http.Server{Addr: ":8080", Handler: handler}
    log.Fatal(srv.ListenAndServe())
}
```

## Multiple Build Variants (Prod vs Dev)

Use separate injector files with `//go:build` constraints to select different provider sets at build time.

```go
// wire_prod.go
//go:build wireinject && !dev

package main

func InitApp() (*App, func(), error) {
    wire.Build(ProdSet, NewApp)
    return nil, nil, nil
}

// wire_dev.go
//go:build wireinject && dev

package main

func InitApp() (*App, func(), error) {
    wire.Build(DevSet, NewApp) // DevSet swaps real DB for in-memory SQLite
    return nil, nil, nil
}
```

Use `-output_file_prefix` to write separate output files — both commands would otherwise overwrite the same `wire_gen.go`:

```bash
wire -tags dev -output_file_prefix=wire_gen_dev gen .
wire -output_file_prefix=wire_gen_prod gen .
```

Add build constraints to the generated files so only one compiles per build:

```go
// wire_gen_prod.go — add at the top (after wire writes it)
//go:build !dev

// wire_gen_dev.go — add at the top
//go:build dev
```

Commit both generated files. At build time, only the matching file is compiled.

## Cleanup-Heavy Graph

When several providers need shutdown coordination, wire's reverse-order cleanup is essential.

```go
// Providers return (T, func(), error)
func NewDBPool(cfg *Config) (*pgxpool.Pool, func(), error) {
    pool, err := pgxpool.New(context.Background(), cfg.DSN)
    if err != nil { return nil, nil, err }
    return pool, func() { pool.Close() }, nil
}

func NewOTelExporter(cfg *Config) (*otlptrace.Exporter, func(), error) {
    exp, err := otlptracegrpc.New(context.Background(), ...)
    if err != nil { return nil, nil, err }
    return exp, func() { exp.Shutdown(context.Background()) }, nil
}

func NewTracerProvider(exp *otlptrace.Exporter) (*trace.TracerProvider, func(), error) {
    tp := trace.NewTracerProvider(trace.WithBatcher(exp))
    return tp, func() { tp.Shutdown(context.Background()) }, nil
}
```

Wire generates shutdown in reverse: `TracerProvider` → `OTelExporter` → `DBPool`. Each cleanup runs before its dependencies shut down — guaranteeing in-flight spans are flushed before the exporter closes.

## Embedding Wire in a CLI

Wire produces a struct, not an app framework. You control the lifecycle:

```go
// wire.go
//go:build wireinject

package cmd

func InitServer(cfg *Config) (*http.Server, func(), error) {
    wire.Build(InfraSet, ServiceSet, NewHTTPServer)
    return nil, nil, nil
}

// cmd/serve.go
func runServe(cfg *Config) error {
    srv, cleanup, err := InitServer(cfg)
    if err != nil { return err }
    defer cleanup()

    quit := make(chan os.Signal, 1)
    signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)

    go func() {
        if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
            log.Fatal(err)
        }
    }()
    <-quit
    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()
    return srv.Shutdown(ctx)
}
```

Unlike `fx.Run()`, wire does not manage the lifecycle loop. Implement signal handling and graceful shutdown explicitly. This is a feature for CLI tools that spin up short-lived services and need precise control over the shutdown sequence.

## Passing External Values to Wire

Values built before wire runs (parsed config, test doubles) become injector parameters:

```go
//go:build wireinject

// cfg is resolved outside the graph — treated as a provided *Config
func InitApp(cfg *Config) (*App, func(), error) {
    wire.Build(InfraSet, ServiceSet, NewApp)
    return nil, nil, nil
}

// main.go
cfg, err := config.Load()
if err != nil { log.Fatal(err) }
app, cleanup, err := InitApp(cfg)
```

The parameter `cfg *Config` satisfies any downstream provider that requests `*Config` — no `wire.Value` or extra set entry needed.
references/testing.md
# Testing — google/wire

Wire generates plain Go constructor calls, so tests work directly on the constructor layer — no container API to learn.

## Unit Tests: Plain Constructor Injection

The generated code has no wire dependency. Test constructors directly:

```go
func TestUserService_GetUser(t *testing.T) {
    mockStore := &MockUserStore{users: map[int64]*User{1: &User{ID: 1, Name: "Alice"}}}
    cache := newTestRedis(t)
    svc := service.NewUserService(mockStore, cache)

    u, err := svc.GetUser(context.Background(), 1)
    require.NoError(t, err)
    assert.Equal(t, "Alice", u.Name)
}
```

Pass mocks directly as constructor arguments. No wire, no container, no file to generate. This is the idiomatic approach for unit tests.

## Test Injectors: Swapping Providers

For integration or component tests where you want the full wired graph but with selected dependencies replaced, create a test-only injector in a `_test.go` file.

```go
// app_test.go
//go:build wireinject

package main

import (
    "testing"
    "github.com/google/wire"
)

// TestSet replaces real infra with in-memory fakes
var TestSet = wire.NewSet(
    NewTestConfig,
    NewInMemoryUserStore,
    wire.Bind(new(repo.UserStore), new(*InMemoryUserStore)),
    NewTestRedis,
)

func InitTestApp(t *testing.T) (*App, func(), error) {
    wire.Build(TestSet, service.ServiceSet, NewApp)
    return nil, nil, nil
}
```

```go
// app_integration_test.go
//go:build !wireinject  // compiles when the wireinject tag is NOT set

package main

func TestApp_GetUser(t *testing.T) {
    app, cleanup, err := InitTestApp(t)
    require.NoError(t, err)
    defer cleanup()

    // test against the fully-wired app with fake dependencies
    u, err := app.GetUser(context.Background(), 1)
    require.NoError(t, err)
    assert.NotNil(t, u)
}
```

Run `wire ./...` to generate `wire_gen.go` — the test injector is included because the `_test.go` file is compiled as part of the package during `go test`.

**Key pattern from upstream best practices:** Prefer creating a test-only provider set over passing mocks as injector arguments (though both work). The set approach keeps the test injector composable.

## Passing Mocks as Injector Arguments

An alternative to a test set: pass the mock directly as an injector parameter. Wire treats it as a pre-built provider.

```go
//go:build wireinject

func InitTestApp(store repo.UserStore) (*App, func(), error) {
    wire.Build(config.ConfigSet, service.ServiceSet, NewApp)
    return nil, nil, nil
}

// Test
func TestApp(t *testing.T) {
    mock := &MockUserStore{}
    app, cleanup, err := InitTestApp(mock)
    require.NoError(t, err)
    defer cleanup()
    // ...
}
```

Use this form when you only need to replace one or two dependencies and a full `TestSet` is overkill.

## CI: Detecting Stale `wire_gen.go`

If `wire_gen.go` is not regenerated after a provider change, CI builds pass but the graph is wrong. Enforce freshness in CI:

```bash
# Option 1: re-run wire and check for diffs
wire ./...
git diff --exit-code -- '**/wire_gen.go'
```

```yaml
# .github/workflows/ci.yml
- name: Check wire_gen.go is up-to-date
  run: |
    go install github.com/google/wire/cmd/wire@v0.7.0
    wire ./...
    git diff --exit-code -- '**/wire_gen.go'
```

```bash
# Option 2: use wire check (verifies graph without regenerating)
wire check ./...
```

`wire check` exits non-zero if the graph is inconsistent but does **not** update `wire_gen.go`. Use it for a fast graph-validity check without modifying files.

## Testing Interface Bindings

`wire.Bind` can be used in test sets to bind a fake to the same interface:

```go
// Fake implements the same interface as the real provider
type FakeMailer struct{ sent []string }
func (f *FakeMailer) Send(to, body string) error { f.sent = append(f.sent, to); return nil }

var TestMailerSet = wire.NewSet(
    NewFakeMailer,
    wire.Bind(new(notification.Mailer), new(*FakeMailer)),
)

var TestSet = wire.NewSet(
    TestMailerSet,
    realServiceSet,  // everything else is real
)
```

This keeps the test injector narrow — only the Mailer is faked; the rest of the graph is real.

## Table-Driven Tests Without Wire

Wire is an initialization tool. Once the object graph is built, table-driven tests on individual services need no wire involvement:

```go
func TestUserService(t *testing.T) {
    cases := []struct {
        name  string
        id    int64
        users map[int64]*User
        want  string
        err   bool
    }{
        {"found", 1, map[int64]*User{1: &User{Name: "Alice"}}, "Alice", false},
        {"not found", 99, nil, "", true},
    }
    for _, tc := range cases {
        t.Run(tc.name, func(t *testing.T) {
            svc := service.NewUserService(&MockUserStore{users: tc.users}, nil)
            u, err := svc.GetUser(context.Background(), tc.id)
            if tc.err { require.Error(t, err); return }
            assert.Equal(t, tc.want, u.Name)
        })
    }
}
```

Wire has no role here — the injector was only needed to build the object graph in `main` (or in an integration test). Unit tests construct dependencies directly.