Skip to main content

samber/do vs google/wire

Verdict: pick google/wire if you want zero runtime cost and are fine running a code-generation step (wire CLI) on every dependency change, with a fully static graph known at compile time. Pick samber/do if you want compile-time type safety without a build step, plus runtime flexibility (conditional registration, scopes, overriding services at runtime) that generated code can't easily express.

At a glance

samber/dogoogle/wire
MechanismGo 1.18+ generics, resolved at runtimeCode generation (wire CLI), resolved at compile time
Build step requiredNoYes — regenerate wire_gen.go on every graph change
Type safetyCompile-time (via generics)Compile-time (generated code is plain Go)
Runtime flexibilityFull — conditional registration, override, scopesNone — the graph is fixed at generation time
Runtime costSmall map lookups per InvokeNone — generated code is direct function calls
Lifecycle managementHealth checks, graceful shutdown, hooksNone — you wire cleanup manually
Debugging toolsScope tree, dependency graph, Web UIRead the generated wire_gen.go
Tooling dependencyNonewire binary must be installed and run in CI

API style

wire doesn't run at runtime at all — you write "injector" functions annotated with wire.Build, and the wire command generates the real implementation as plain Go:

//go:build wireinject

func InitializeApp() (*App, error) {
wire.Build(NewDatabase, NewUserService, NewApp)
return &App{}, nil // never actually executed
}

Running wire produces wire_gen.go, a hand-written-looking file with no reflection and no wire import at runtime. samber/do skips the generation step entirely — the same constructor functions are registered and resolved directly:

injector := do.New()

do.Provide(injector, NewDatabase)
do.Provide(injector, NewUserService)

app, err := do.Invoke[*App](injector)

Compile-time safety vs runtime flexibility

Both libraries catch dependency-graph mistakes before your code reaches production, but at different points. wire fails at go generate time if a dependency is missing or ambiguous — you can't even compile without fixing it. samber/do fails at go build time for type mismatches (thanks to generics) and at do.Invoke time for missing registrations — slightly later, but still before the request path in most apps, since services are typically invoked once at startup.

The tradeoff is flexibility: wire's graph is fixed once generated, so conditional wiring (feature flags, per-environment services, request-scoped overrides) requires generating multiple injector functions or falling back to manual code. samber/do's graph is built at runtime, so you can register services conditionally, override them for tests, or attach scopes dynamically — patterns that don't fit wire's static model.

Lifecycle and health checks

wire has no opinion on lifecycle — it only wires constructors together. Cleanup functions (the second return value some wire providers support) are called once, in wire_gen.go, with no health check or dependency-aware shutdown ordering. samber/do provides both: a Healthchecker interface for on-demand health probing, and a Shutdowner interface for reverse-order graceful shutdown across the whole scope tree.

Testing

wire tests typically generate a second injector function (e.g. InitializeTestApp) wired to mocks — meaning a second wire.Build call and a second generated file to keep in sync. samber/do avoids the duplication: Clone() an existing injector and do.Override the services you need to fake, in the test file itself.

Choose google/wire if:

  • You want zero runtime dependency-injection cost — the generated code is indistinguishable from hand-written Go.
  • Your dependency graph is static and known entirely at compile time.
  • You're comfortable adding wire to your build/CI pipeline and committing generated code.

Choose samber/do if:

  • You don't want a code-generation step in your build.
  • You need runtime flexibility: conditional registration, scopes, or swapping services without regenerating code.
  • You want built-in health checks and graceful shutdown, not just constructor wiring.

Already using google/wire? See the migration guide from Google Wire for a step-by-step conversion of provider sets and bindings.