-
Notifications
You must be signed in to change notification settings - Fork 0
Limitations
These are design choices, not gaps waiting to be filled. Each one is here because the alternative costs more than it gives.
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.
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.
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.
Construction cannot report an error. Do failable work before registration and
register the result with Instance, or panic in the factory. See
Registration → There is no (T, error) factory.
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.
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, andUnbindis the supported way to replace one. See Lifetimes.
dix — Dependency Injection eXperience · MIT · © 2026 Kryovyx · pre-1.0, interfaces may change
Concepts
Reference
Practice
Ecosystem