Documentation
¶
Overview ¶
Package di is the dependency-injection container: typed, lazy providers resolved through Go generics, with no reflection and no struct tags.
What it gives you over wiring by hand in a bootstrap function:
- Providers are LAZY. A dependency is constructed the first time something asks for it, exactly once, even under concurrent resolution.
- Cycles are REPORTED, with the full path, instead of deadlocking or overflowing the stack.
- Teardown is ORDERED. Anything that implements Closer is closed in the reverse of its construction order, so a pool never outlives the thing draining into it.
What it deliberately does not do: reflection-based autowiring by struct tag. A constructor asks for what it needs, in code, and a missing dependency is a boot-time error naming both sides. Magic that fails at runtime is worse than a line of wiring.
di.Provide(c, func(s *di.Scope) (*Repo, error) {
return NewRepo(di.MustResolve[*gorm.DB](s)), nil
})
repo := di.MustResolve[*Repo](c)
Example ¶
A constructor asks for what it needs, in code. Nothing is resolved by reflection or by struct tag.
package main
import (
"fmt"
"github.com/IncepTools/go/src/core/di"
)
type database struct{ dsn string }
type productRepo struct{ db *database }
type productService struct{ repo *productRepo }
func (s *productService) Describe() string { return "service on " + s.repo.db.dsn }
func main() {
c := di.New()
di.MustSupply(c, &database{dsn: "postgres://db/shop"})
di.MustProvide(c, func(s *di.Scope) (*productRepo, error) {
return &productRepo{db: di.MustResolve[*database](s)}, nil
})
di.MustProvide(c, func(s *di.Scope) (*productService, error) {
return &productService{repo: di.MustResolve[*productRepo](s)}, nil
})
fmt.Println(di.MustResolve[*productService](c).Describe())
}
Output: service on postgres://db/shop
Index ¶
- Variables
- func Has[T any](c *Container) bool
- func Invoke(c *Container, fn func(*Scope) error) error
- func MustProvide[T any](c *Container, ctor Constructor[T])
- func MustProvideNamed[T any](c *Container, name string, ctor Constructor[T])
- func MustResolve[T any](r Resolver) T
- func MustResolveNamed[T any](r Resolver, name string) T
- func MustSupply[T any](c *Container, v T)
- func Provide[T any](c *Container, ctor Constructor[T]) error
- func ProvideNamed[T any](c *Container, name string, ctor Constructor[T]) error
- func Resolve[T any](r Resolver) (T, error)
- func ResolveNamed[T any](r Resolver, name string) (T, error)
- func Supply[T any](c *Container, v T) error
- func SupplyNamed[T any](c *Container, name string, v T) error
- type Closer
- type Constructor
- type Container
- type Resolver
- type Scope
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ErrClosed = errors.New("di: container is closed")
ErrClosed is returned by a resolve or a provide after Close.
var ErrCycle = errors.New("di: dependency cycle")
ErrCycle is returned when providers depend on each other in a loop.
var ErrNotProvided = errors.New("di: no provider registered")
ErrNotProvided is returned when nothing provides the requested type.
Functions ¶
func Invoke ¶
Invoke runs fn with a resolution scope. Use it for boot-time work that needs several dependencies but produces nothing worth registering.
func MustProvide ¶
func MustProvide[T any](c *Container, ctor Constructor[T])
MustProvide is Provide, panicking on a duplicate registration. Registration happens at boot from code you wrote, so a panic there is a compile-time-ish failure, not a runtime risk.
func MustProvideNamed ¶
func MustProvideNamed[T any](c *Container, name string, ctor Constructor[T])
MustProvideNamed is ProvideNamed, panicking on a duplicate registration.
func MustResolve ¶
MustResolve is Resolve, panicking on failure.
It is the right call inside a constructor and inside module wiring, which run at boot: a missing provider there should stop the process, loudly, not be handled. It is the wrong call in a request path.
func MustResolveNamed ¶
MustResolveNamed is ResolveNamed, panicking on failure.
func MustSupply ¶
MustSupply is Supply, panicking on a duplicate registration.
func Provide ¶
func Provide[T any](c *Container, ctor Constructor[T]) error
Provide registers a lazy singleton constructor for T.
Registering the same T twice is an error rather than a silent overwrite: two providers for one type means two different objects depending on resolution order, which is a bug that only shows up in production. Use a name when you genuinely want two (a primary and a replica pool, say).
Example (ConstructsOnce) ¶
Providers are lazy and run exactly once, however many times the value is resolved.
package main
import (
"fmt"
"github.com/IncepTools/go/src/core/di"
)
type database struct{ dsn string }
func main() {
c := di.New()
built := 0
di.MustProvide(c, func(*di.Scope) (*database, error) {
built++
return &database{dsn: "x"}, nil
})
fmt.Println("before any resolve:", built)
_ = di.MustResolve[*database](c)
_ = di.MustResolve[*database](c)
fmt.Println("after two resolves:", built)
}
Output: before any resolve: 0 after two resolves: 1
func ProvideNamed ¶
func ProvideNamed[T any](c *Container, name string, ctor Constructor[T]) error
ProvideNamed registers a lazy singleton for T under a name, so one type can have several distinct bindings.
func Resolve ¶
Resolve returns the instance of T, constructing it if this is the first ask.
Example (Cycle) ¶
A cycle is reported with the path that caused it, rather than deadlocking or overflowing the stack.
package main
import (
"errors"
"fmt"
"github.com/IncepTools/go/src/core/di"
)
func main() {
type a struct{}
type b struct{}
c := di.New()
di.MustProvide(c, func(s *di.Scope) (*a, error) {
_, err := di.Resolve[*b](s)
return &a{}, err
})
di.MustProvide(c, func(s *di.Scope) (*b, error) {
_, err := di.Resolve[*a](s)
return &b{}, err
})
_, err := di.Resolve[*a](c)
fmt.Println(errors.Is(err, di.ErrCycle))
}
Output: true
func ResolveNamed ¶
ResolveNamed returns the named instance of T.
func Supply ¶
Supply registers an already-constructed value. Use it for things built before the container exists — the parsed config, the logger, a *gorm.DB the caller opened.
Example (Interface) ¶
Register an implementation under the interface its consumers ask for. Without the explicit type parameter the binding would be registered under the concrete type and nothing asking for the interface would find it.
package main
import (
"fmt"
"github.com/IncepTools/go/src/core/di"
)
func main() {
type Clock interface{ Now() string }
c := di.New()
di.MustSupply[Clock](c, fixedClock{})
fmt.Println(di.MustResolve[Clock](c).Now())
}
type fixedClock struct{}
func (fixedClock) Now() string { return "2026-01-01T00:00:00Z" }
Output: 2026-01-01T00:00:00Z
Types ¶
type Closer ¶
Closer is implemented by a provider whose instance holds something that must be released: a pool, a client, a file, a background loop. Container.Close calls it in reverse construction order.
A plain io.Closer is honoured too.
type Constructor ¶
Constructor builds one instance. It receives a *Scope rather than the container so that resolving a dependency from inside it extends the resolution path, which is what makes cycle reporting possible.
type Container ¶
type Container struct {
// contains filtered or unexported fields
}
Container holds providers and the instances built from them.
The zero value is not usable; call New.
func (*Container) Close ¶
Close releases every constructed instance that implements Closer or io.Closer, in the reverse of construction order, and marks the container unusable. It is safe to call more than once; later calls return the first result.
func (*Container) Eager ¶
Eager constructs every registered provider now, instead of on first use.
Call it at the end of boot. A provider that fails — a bad DSN, an unreachable broker — then fails the deploy rather than the first request that happens to need it.
Example ¶
Eager constructs everything before the listener opens, so a bad dependency fails the deploy rather than the first request that needs it.
package main
import (
"context"
"errors"
"fmt"
"github.com/IncepTools/go/src/core/di"
)
type database struct{ dsn string }
func main() {
c := di.New()
di.MustProvide(c, func(*di.Scope) (*database, error) {
return nil, errors.New("could not parse the DSN")
})
fmt.Println(c.Eager() != nil)
_ = c.Close(context.Background())
}
Output: true
type Resolver ¶
type Resolver interface {
// contains filtered or unexported methods
}
Resolver is satisfied by *Container and by the *Scope handed to a constructor. It cannot be implemented outside this package.
type Scope ¶
type Scope struct {
// contains filtered or unexported fields
}
Scope is a resolution in progress. It carries the chain of types being constructed so that a cycle can be reported as a path rather than a hang.