di

package
v1.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 7, 2026 License: Apache-2.0 Imports: 7 Imported by: 0

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

Examples

Constants

This section is empty.

Variables

View Source
var ErrClosed = errors.New("di: container is closed")

ErrClosed is returned by a resolve or a provide after Close.

View Source
var ErrCycle = errors.New("di: dependency cycle")

ErrCycle is returned when providers depend on each other in a loop.

View Source
var ErrNotProvided = errors.New("di: no provider registered")

ErrNotProvided is returned when nothing provides the requested type.

Functions

func Has

func Has[T any](c *Container) bool

Has reports whether T is provided, without constructing it.

func Invoke

func Invoke(c *Container, fn func(*Scope) error) error

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

func MustResolve[T any](r Resolver) T

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

func MustResolveNamed[T any](r Resolver, name string) T

MustResolveNamed is ResolveNamed, panicking on failure.

func MustSupply

func MustSupply[T any](c *Container, v T)

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

func Resolve[T any](r Resolver) (T, error)

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

func ResolveNamed[T any](r Resolver, name string) (T, error)

ResolveNamed returns the named instance of T.

func Supply

func Supply[T any](c *Container, v T) error

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

func SupplyNamed

func SupplyNamed[T any](c *Container, name string, v T) error

SupplyNamed registers an already-constructed value under a name.

Types

type Closer

type Closer interface {
	Close(ctx context.Context) error
}

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

type Constructor[T any] func(*Scope) (T, error)

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 New

func New() *Container

New returns an empty container.

func (*Container) Close

func (c *Container) Close(ctx context.Context) error

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

func (c *Container) Eager() error

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

func (*Container) Providers

func (c *Container) Providers() []string

Providers lists every registered binding, for diagnostics.

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.

func NewScope

func NewScope(c *Container) *Scope

NewScope returns a resolution scope for c. Use it where a *Scope is required outside a constructor — module construction, boot-time wiring.

func (*Scope) Container

func (s *Scope) Container() *Container

Container returns the container this scope resolves against.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL