Skip to content
wiki edited this page Sep 4, 2026 · 1 revision

Errors

Every failure mode is a package-level sentinel, so callers branch with errors.Is rather than matching on message text. Returned errors are wrapped with the offending type, so the message is useful while the identity is stable.

var svc *UserService
if err := scope.Resolve(&svc); err != nil {
	switch {
	case errors.Is(err, dix.ErrNotRegistered):
		// wiring gap
	case errors.Is(err, dix.ErrScopeClosed):
		// used after the request ended
	default:
		return err
	}
}

The sentinels

ErrNotRegistered

Nothing in the container satisfies the requested type. For an interface request, it means no registered concrete type implements it — check that the implementation is registered, not just that the interface exists.

ErrScopedFromRoot

The requested type is registered as Scoped and was resolved from the root container. The container deliberately does not build one: a scoped value with no owning scope is a value nothing will ever Close.

scope := container.NewScope()
defer scope.Close()
var svc *UnitOfWork
err := scope.Resolve(&svc) // resolve from the scope

ErrAmbiguousResolution

More than one registration satisfies the requested interface; the message names every candidate. Returning one of them would make the winner depend on Go's randomised map iteration order, so the same binary would resolve differently from run to run. See Resolution.

ErrAlreadyRegistered

The type already has a registration, under this lifetime or another. Use Unbind first to replace it deliberately.

ErrInvalidFactory

The value passed to Singleton, Scoped or Transient is not an accepted factory signature — or a nil or a function was passed to Instance. See Registration for the two accepted shapes.

ErrInvalidTarget

Resolve was given something that is not a pointer, or ResolveAll something that is not a pointer to a slice of interfaces.

ErrScopeClosed

The Scope was used after Close. Also returned when a resolve loses a race with Close — the value that was under construction is closed rather than handed back.

Why sentinels at all

Message matching couples a caller to another module's exact wording, and it breaks silently when that wording is improved. Elsewhere in this ecosystem extensions were reduced to strings.Contains(err.Error(), "already exists") before the framework grew its own sentinels; dix avoids that by construction.

Clone this wiki locally