· via dev.to (home feed)
kanna-di offers provider-free compile-time DI for Go as wire takes no new features
A dev.to post by the tool's author introduces kanna-di, a Go code generator that does compile-time dependency injection without provider registration, pitched as a successor to the feature-frozen google/wire.

A post on dev.to introduces kanna-di, a compile-time dependency injection code generator for Go written by the post's author, who frames it as an answer to a question that keeps coming up: google/wire has stopped accepting new features, so what comes after it? Like wire, kanna-di emits ordinary Go source and imports nothing at run time. The departure is that it does away with provider registration entirely.
The registration work wire requires
The author grounds the motivation in what a wire setup demands: a separate wire.go file behind a //go:build wireinject tag where each injector's signature is written by hand, provider sets assembled through wire.NewSet and handed to wire.Build, a wire.Bind entry for every interface, and a return trip to some set whenever a new constructor appears. Constructors already sit next to the types they build, the post argues, so wire effectively asks you to maintain a second, separate inventory of them.
Resolution by convention
kanna-di inverts that arrangement. You annotate the struct that acts as your composition root with //kanna:container and run go generate. The generator inspects each field's type, locates the constructor associated with that type, follows that constructor's parameters recursively, and writes a di_gen.go file containing a function that builds the whole graph and returns the struct along with a cleanup function and an error.
Decisions only the root can make are expressed as struct tags beside it: di:"with=postgres.NewUserRepository" picks which implementation backs a domain interface, and a hand-written helper such as openDB supplies standard-library values like a *sql.DB that have no constructor of their own. A config value loaded once is shared across everything depending on it, and when a constructor's parameter list changes, the next generation pass rewrites the calling code.
Provider lookup follows a fixed precedence, spelled out in the post: an argument to the container (di:"arg"), the provider chosen with di:"with=...", an exported field of a struct the container embeds or declares as blank, a function in the container's own package, and finally an exported function in the package that declares the type, provided that package lives inside your module or go.work. Generation halts when nothing matches, and also when two candidates surface at the same level. The standard library and external modules are deliberately never searched — the post observes that io contains functions returning an io.Writer, and you would not want one picked for you silently.
Errors that point at the field
Diagnostics get first-class treatment because failed resolution is where DI tooling consumes the most time. Errors trace a missing dependency back to the container field where the chain started and list the path through the constructors involved. Ambiguous cases show both candidate functions with their file positions plus the tag that would disambiguate them. Misspelled names trigger a did-you-mean suggestion, and an implementation selected via with= that fails to satisfy the interface produces an error naming the missing method.
A different testing story
The sharpest contrast with wire, in the author's view, is test composition. Swapping in fakes under wire means building another injector and another provider set. With kanna-di, a _test.go file declares its own container that embeds the production one and promotes chosen interfaces to di:"arg" arguments; the generated test constructor then accepts the replacements directly. Because the production bindings go unused, infrastructure work such as opening the database, loading configuration and constructing the external mail client never runs during the test.
Why it matters
wire remains widely used, but its feature freeze leaves Go teams without an obvious path forward, and the question of a successor surfaces regularly. kanna-di keeps the property that makes wire attractive — generated plain Go with zero run-time imports — while removing the parallel provider registry that drives most of its upkeep. Two caveats are worth holding onto: the comparison comes from the tool's own author, so it reads as an introduction rather than a neutral evaluation, and convention-driven lookup trades explicit registration for implicit discovery, a trade-off teams that prize explicit wiring may decline. For anyone maintaining a wire codebase, it is at minimum a serious data point in the debate over what compile-time DI in Go should look like.
- #golang
- #dependency-injection
- #code-generation
- #open-source