Skip to content

Limitations

wiki edited this page Sep 4, 2026 · 1 revision

Limitations

These are design choices, not gaps waiting to be filled. Each one is here because the alternative costs more than it gives.

No constructor injection

Dependencies are resolved inside a factory, by hand:

c.Singleton(func(r dix.Resolver) *UserService {
	var repo *UserRepo
	if err := r.Resolve(&repo); err != nil {
		panic(err)
	}
	return &UserService{Repo: repo}
})

There is no func(repo *UserRepo) *UserService form where the container fills in the parameters. Auto-wiring makes the dependency graph implicit: you cannot see what a type needs without running the program, and a missing registration becomes a startup panic in reflection code rather than a line you can read. The explicit form is three lines longer and always greppable.

No cycle detection

A needing B needing A recurses until the goroutine stack is exhausted. The container does not detect it, and the failure looks like a stack overflow rather than a wiring error.

If you hit one, the cycle is usually real and worth breaking rather than tolerating — introduce an interface one side depends on, or move the shared state into a third type both depend on.

No named or tagged bindings

The Go type is the only key. Two things of the same type that mean different things need two types:

type ReadDB struct{ *sql.DB }
type WriteDB struct{ *sql.DB }

This is more verbose than Named("read"), and it is checked by the compiler.

No (T, error) factories

Construction cannot report an error. Do failable work before registration and register the result with Instance, or panic in the factory. See RegistrationThere is no (T, error) factory.

Reflection at resolve time

Every Resolve does a type lookup through reflect. For request-scoped resolution — a handful of calls per request — this is not measurable next to the HTTP work around it. For a hot inner loop, resolve once outside the loop and keep the value.

Nothing closes singletons

Scope.Close releases scoped instances only. A singleton holding a connection pool must be closed by the code that owns the application's shutdown. The container has no Close of its own, deliberately: it does not know whether the process is ending or whether something else still holds the value.


A note on an older README. Earlier documentation listed "registering the same type twice overwrites the previous registration" as a limitation. That has not been true for some time — a second registration returns ErrAlreadyRegistered, and Unbind is the supported way to replace one. See Lifetimes.

Clone this wiki locally