Google Wire Is Dead and Uber Fx Panics at Boot — A Compile-Time DI Library Steps In
In August 2025, Google Wire was archived by its official maintainers. The project once hailed as the "compile-time dependency injection standard" is dead.
A friend in a group chat immediately blew up: "I just finished writing my entire project with Wire, and you're telling me it's archived?"
Calm down. Wire still works, but don't expect new features. The key point is, it wasn't that great to use in the first place.
Three moments that make Wire maddening
First breakdown: return nil, nil
func InitApp() (*App, error) {
wire.Build(NewDB, NewService, NewHandler)
return nil, nil // This line never executes
}
The first time you see this code, are you also confused? It declares a return of *App, but the result is return nil. Why? Because wire.Build is a compile-time marker and doesn't run at runtime. But Go's syntax requires a return value, so you have to write this line of nonsense.
This isn't a bug. It's a feature. An anti-human feature.
Second breakdown: No startup hooks
Wire does only one thing: constructs your objects. Then what? Starting services, connecting to databases, registering routes... you have to write all of that yourself.
So your main.go turns into this:
func main() {
app, _ := InitApp()
app.DB.Connect()
app.Server.Start()
app.Router.Register()
app.Cache.Warmup()
app.Metrics.Export()
// Miss one, and it blows up in production
}
Every time you add a new component, you have to manually add a line. Forget one? No one reminds you; it goes straight to production and explodes.
Third breakdown: No generics support
Go 1.18 has been out for three years, and Wire's support for generics is still "manual wrapping."
// You want to write this?
dig.Provide(NewStore[int]) // Native generics, write it directly
// Wire requires this:
func provideStoreInt() *Store[int] { return NewStore[int]() }
var Set = wire.NewSet(provideStoreInt)
Write a wrapper function for every type. The code bloats like a tourist attraction during a national holiday.
So Fx is perfect? Think again
Fx's API is indeed beautiful, one dependency per line, pleasing to the eye. But at startup, you never know what will happen:
panic: missing dependency: *DB (did you forget to provide it?)
Compilation passed, tests ran, and it panics on startup.
Why? Fx relies on runtime reflection. Dependencies are not checked at compile time; all errors wait for you at startup.
The larger the project, the slower the startup. Because it has to reflect and parse the entire dependency tree every time. Change one line of code locally, wait half a day for a restart, and development efficiency is cut in half.
Interface return + concrete injection = time bomb
fx.Provide(func() interface{} { return &DB{} })
fx.Invoke(func(db *DB) { ... })
// Startup panic: missing dependency
Type mismatch? Can't see it at compile time; it crashes directly on startup.
This "compiles but panics on startup" experience, anyone who has used it understands.
So what to use? Check out dig
dig is a compile-time DI framework. Its syntax is like Fx, its safety is like Wire, but without Wire's problems.
//go:build digen
func InitApp() func(context.Context) error {
return dig.Build(
dig.Provide(NewConfig), // Normal constructor
dig.Provide(NewDB),
dig.Supply(DefaultTimeout), // Inject value directly, no wrapping
dig.Provide(func(t Timeout) *Server { return NewServer(t) }),
dig.Invoke(func(srv *Server) error { return srv.Run() }),
)
}
Note: No return nil, nil.
No reflection, all compile-time code generation.
How does dig solve these pain points?
1. No dummy placeholders
dig.Build directly returns an executable function:
func InitApp() func(context.Context) error {
return dig.Build(...) // Returns a function, not a dummy placeholder
}
Intuitive, even a beginner can understand it.
2. Native generics support
dig.Provide(NewStore[int]) // Pass generic type directly
dig.Provide(NewStore[string])
No need to write wrapper functions; the code is clean.
3. Built-in Invoke, no manual maintenance checklist
dig.Invoke(func(db *DB) error { return db.Ping() })
dig.Invoke(func(srv *Server) error { return srv.Start() })
Automatically executes when dependencies are ready. Add one line of Invoke for a new component, and you'll never forget.
4. Closure capture checking
Wrote an inline closure in Provide and accidentally captured a local variable? go generate reports an error directly:
❌ cannot capture local variable "t"
Wire silently passes this, then generates code that fails to compile, and you can't even see where the problem is.
5. Compile-time checking
go generate ./...
# Missing dependency? Circular reference? Type conflict? Errors are reported right here, no need to wait until runtime
Compile-time vs. Runtime: The essential difference
| Dimension | dig / Wire | Fx |
|---|---|---|
| Dependency resolution timing | go generate phase |
Program startup (reflection) |
| Dependency error discovery | Errors reported during code generation, won't even compile | Runtime panic, crashes on startup |
| Runtime overhead | Pure Go function calls, no extra overhead | Reflection parses types, builds container, extra CPU + memory overhead |
| Binary size | Pure generated Go code, no extra metadata | Carries type info and reflection metadata, larger size |
dig and Wire are compile-time solutions:
- Dependency resolution completes during the
go generatephase; errors are intercepted when code is generated - At runtime, only pure Go function calls exist, no reflection lookups or type resolution
- Startup speed = speed of hand-written
main.go, no framework overhead
Fx is a runtime solution:
- Only at program startup does it use reflection to parse Provider signatures and build the dependency graph
- Every startup has parsing overhead, more noticeable with more dependencies
- Runtime reflection means: missing dependencies, type conflicts, circular references... all exposed only at startup
Technical comparison
Note: ✅ indicates feature support, ❌ indicates no support, ⚠️ indicates support with limitations or costs, N/A indicates not applicable.
| Dimension | dig | Wire | Fx |
|---|---|---|---|
| Positioning | Compile-time generation | Compile-time generation | Runtime reflection |
wire.Build dummy placeholder |
❌ Not needed | ❌ Must write return nil |
❌ Not needed |
| Generic Provider | ✅ Native support | ❌ Not supported (manual wrapping required) | ⚠️ Reflection support, with overhead |
| Startup hooks (Invoke) | ✅ Built-in | ❌ None (manual maintenance) | ✅ Built-in |
| Closure capture check | ✅ Mandatory check | ❌ No check | N/A |
| Module definition method | Functions, can pass parameters | Package-level variables, inflexible | Functions, can pass parameters |
| Official maintenance status | ✅ Actively maintained | ❌ Archived (2025.8) | ✅ Actively maintained |
Is the migration cost high?
No.
- The API is basically the same as Fx; Fx users can switch seamlessly.
- The generated code is pure Go and does not depend on the dig runtime.
- Get started in 5 minutes:
go get github.com/shanjunmei/[email protected]
go install github.com/shanjunmei/dig/cmd/digen@latest
Final words
Google archived Wire, and Fx is still holding on with reflection.
If you:
- Are fed up with
return nil, nil - Don't want to maintain a manual initialization checklist
- Don't want to see Fx's runtime panics
- Want compile-time safety + Fx's API experience
dig is worth 5 minutes of your time to try.