Skip to content

Resolution

wiki edited this page Sep 4, 2026 · 1 revision

Resolution

Resolve(target any) error     // *target = the one match
ResolveAll(target any) error  // append every match

Resolve

target must be a pointer. Anything else is ErrInvalidTarget.

var db *Database
if err := c.Resolve(&db); err != nil { ... }

The pointed-to type is the request. &db where db is *Database asks for *Database; &log where log is a Logger interface asks for something assignable to Logger.

Matching by interface

Requesting an interface matches any registered concrete type that implements it. Requesting a concrete type matches only that exact type.

type Logger interface{ Log(string) }

c.Instance(&ConsoleLogger{}) // registers *ConsoleLogger

var l Logger
_ = c.Resolve(&l) // matches *ConsoleLogger

Two matches is an error, not a coin flip

If more than one registration satisfies the requested interface, resolution fails with ErrAmbiguousResolution, and the message names every candidate.

This is deliberate, and it is not fussiness. Registrations live in a map, and Go randomises map iteration order — so "return the first match" would resolve a different implementation on each process start. With two loggers registered, which one a binary used would change from boot to boot, and the bug would reproduce roughly half the time.

Fix it by registering one, or by resolving the concrete type:

var l *ConsoleLogger
_ = c.Resolve(&l) // unambiguous — exact type

ResolveAll

target must be a pointer to a slice of interfaces. Every registration assignable to the element type is appended.

var checks []HealthCheck
if err := scope.ResolveAll(&checks); err != nil { ... }

Where you call it changes what you get:

Called on Covers Cleanup
the root container singletons and transients nothing scoped is built
a Scope singletons, transients and scoped scoped values are tracked and closed by Close

Scoped registrations are skipped at the root for the same reason Resolve refuses them: a scoped value built from the root would have no owner. Called on a scope, anything scoped that ResolveAll constructs is registered with that scope, so Close releases it — earlier versions built those values and dropped them on the floor, leaking every closer ResolveAll ever touched.

What resolution does not do

  • No cycles are detected. A needing B needing A recurses until the stack is exhausted. See Limitations.
  • No partial construction. A factory that panics propagates the panic to the caller of Resolve; nothing is memoised.
  • No names or tags. The Go type is the only key. Two *sql.DB values that mean different things need two types (type ReadDB struct{ *sql.DB }), not two names.

Clone this wiki locally