Documentation
¶
Overview ¶
Package q is "Go wild with Q, the funkiest -toolexec preprocessor" — a -toolexec preprocessor that implements rejected Go language proposals (the ? / try operator) plus a playground of helpers Go didn't ship: ctx cancellation checkpoints, futures and fan-in, panic→error recovery, mutex sugar, runtime preconditions, dbg!-style prints and slog.Attr builders. Each q.Try / q.NotNil call (and their chain-style siblings q.TryE / q.NotNilE with .Err / .ErrF / .Catch / .Wrap / .Wrapf / .RecoverIs / .RecoverAs methods) is rewritten at compile time into the conventional `if err != nil { return … }` shape — flat call sites, identical generated code to hand-written error forwarding, zero runtime overhead.
Build contract:
Without `-toolexec=q`, the link step fails on the missing _q_atCompileTime symbol (referenced once at package level via //go:linkname). Forgetting the preprocessor is a loud, deterministic build failure, never a silent runtime divergence.
With `-toolexec=q`, every q.* call site is rewritten away before the user's package compiles, so these function bodies do not run in production. If the rewriter ever misses a call (its bug, not the user's), the surviving body panics with a message naming the unrewritten call — loud failure, again, never silent.
IDE story: every function and method below is ordinary Go with a real signature. gopls, go vet, and editors see valid code at all times.
Index ¶
- Variables
- func A[T ~string]() T
- func AllFields[T any]() []string
- func As[T any](x any) T
- func AsOneOf[T any](v any) T
- func AssemblyDebugWriter(ctx context.Context) io.Writer
- func AtCompileTime[R any](fn func() R, codec ...Codec[R]) R
- func AtCompileTimeCode[R any](fn func() string) R
- func Await[T any](f Future[T]) T
- func AwaitAll[T any](futures ...Future[T]) []T
- func AwaitAllCtx[T any](ctx context.Context, futures ...Future[T]) []T
- func AwaitAllRaw[T any](futures ...Future[T]) ([]T, error)
- func AwaitAllRawCtx[T any](ctx context.Context, futures ...Future[T]) ([]T, error)
- func AwaitAny[T any](futures ...Future[T]) T
- func AwaitAnyCtx[T any](ctx context.Context, futures ...Future[T]) T
- func AwaitAnyRaw[T any](futures ...Future[T]) (T, error)
- func AwaitAnyRawCtx[T any](ctx context.Context, futures ...Future[T]) (T, error)
- func AwaitCtx[T any](ctx context.Context, f Future[T]) T
- func AwaitRaw[T any](f Future[T]) (T, error)
- func AwaitRawCtx[T any](ctx context.Context, f Future[T]) (T, error)
- func Camel(s string) string
- func Check(err error)
- func CheckCtx(ctx context.Context)
- func Chunk[T any](slice []T, n int) [][]T
- func Const[T any](v T) func(error) (T, error)
- func ConvertTo[Target, Source any](src Source, opts ...ConvertOption) Target
- func ConvertToE[Target, Source any](src Source, opts ...ConvertOption) (Target, error)
- func Count[T any](slice []T, pred func(T) bool) int
- func Deadline(ctx context.Context, t time.Time) context.Context
- func DebugPrintln[T any](v T) T
- func DebugPrintlnAt[T any](label string, v T) T
- func DebugSlogAttr[T any](v T) slog.Attr
- func Distinct[T comparable](slice []T) []T
- func DistinctBy[T any, K comparable](slice []T, fn func(T) K) []T
- func Drain[T any](ch <-chan T) []T
- func DrainAll[T any](chans ...<-chan T) [][]T
- func DrainAllCtx[T any](ctx context.Context, chans ...<-chan T) [][]T
- func DrainAllRawCtx[T any](ctx context.Context, chans ...<-chan T) ([][]T, error)
- func DrainCtx[T any](ctx context.Context, ch <-chan T) []T
- func DrainRawCtx[T any](ctx context.Context, ch <-chan T) ([]T, error)
- func Drop[T any](slice []T, n int) []T
- func EnumName[T comparable](v T) string
- func EnumNames[T comparable]() []string
- func EnumOrdinal[T comparable](v T) int
- func EnumParse[T comparable](s string) (T, error)
- func EnumValid[T comparable](v T) bool
- func EnumValues[T comparable]() []T
- func Exhaustive[T any](v T) T
- func Exists[T any](slice []T, pred func(T) bool) bool
- func ExistsErr[T any](slice []T, pred func(T) (bool, error)) (bool, error)
- func Expr[T any](v T) string
- func F(format string) string
- func Ferr(format string) error
- func Fields[T any]() []string
- func File() string
- func FileLine() string
- func Filter[T any](slice []T, pred func(T) bool) []T
- func FilterErr[T any](slice []T, pred func(T) (bool, error)) ([]T, error)
- func Find[T any](slice []T, pred func(T) bool) (T, bool)
- func FlatMap[T, R any](slice []T, fn func(T) []R) []R
- func FlatMapErr[T, R any](slice []T, fn func(T) ([]R, error)) ([]R, error)
- func Fln(format string)
- func Fold[T, R any](slice []T, init R, fn func(R, T) R) R
- func FoldErr[T, R any](slice []T, init R, fn func(R, T) (R, error)) (R, error)
- func ForAll[T any](slice []T, pred func(T) bool) bool
- func ForAllErr[T any](slice []T, pred func(T) (bool, error)) (bool, error)
- func ForEach[T any](slice []T, fn func(T))
- func ForEachErr[T any](slice []T, fn func(T) error) error
- func Generator[T any](body func()) iter.Seq[T]
- func GetPar(ctx context.Context) int
- func GoroutineID() uint64
- func GroupBy[T any, K comparable](slice []T, fn func(T) K) map[K][]T
- func InstallSlog(base slog.Handler)
- func InstallSlogJSON(w io.Writer, opts *slog.HandlerOptions)
- func InstallSlogText(w io.Writer, opts *slog.HandlerOptions)
- func Kebab(s string) string
- func Keys[K comparable, V any](m map[K]V) []K
- func Line() int
- func Lock(l sync.Locker)
- func LogCloseErr(err error, recipe string)
- func Lower(s string) string
- func Map[T, R any](slice []T, fn func(T) R) []R
- func MapEntries[K1, K2 comparable, V1, V2 any](m map[K1]V1, fn func(K1, V1) (K2, V2)) map[K2]V2
- func MapEntriesErr[K1, K2 comparable, V1, V2 any](m map[K1]V1, fn func(K1, V1) (K2, V2, error)) (map[K2]V2, error)
- func MapErr[T, R any](slice []T, fn func(T) (R, error)) ([]R, error)
- func MapKeys[K1, K2 comparable, V any](m map[K1]V, fn func(K1) K2) map[K2]V
- func MapKeysErr[K1, K2 comparable, V any](m map[K1]V, fn func(K1) (K2, error)) (map[K2]V, error)
- func MapValues[K comparable, V1, V2 any](m map[K]V1, fn func(V1) V2) map[K]V2
- func MapValuesErr[K comparable, V1, V2 any](m map[K]V1, fn func(V1) (V2, error)) (map[K]V2, error)
- func Match[R any](value any, arms ...MatchArm[R]) R
- func Max[T cmp.Ordered](slice []T) (T, bool)
- func MaxBy[T any, K cmp.Ordered](slice []T, fn func(T) K) (T, bool)
- func Min[T cmp.Ordered](slice []T) (T, bool)
- func MinBy[T any, K cmp.Ordered](slice []T, fn func(T) K) (T, bool)
- func NotNil[T any](p *T) *T
- func Ok[T any](v T, ok bool) T
- func ParExists[T any](ctx context.Context, slice []T, pred func(T) bool) bool
- func ParExistsErr[T any](ctx context.Context, slice []T, pred func(context.Context, T) (bool, error)) (bool, error)
- func ParFilter[T any](ctx context.Context, slice []T, pred func(T) bool) []T
- func ParFilterErr[T any](ctx context.Context, slice []T, pred func(context.Context, T) (bool, error)) ([]T, error)
- func ParFlatMap[T, R any](ctx context.Context, slice []T, fn func(T) []R) []R
- func ParFlatMapErr[T, R any](ctx context.Context, slice []T, fn func(context.Context, T) ([]R, error)) ([]R, error)
- func ParForAll[T any](ctx context.Context, slice []T, pred func(T) bool) bool
- func ParForAllErr[T any](ctx context.Context, slice []T, pred func(context.Context, T) (bool, error)) (bool, error)
- func ParForEach[T any](ctx context.Context, slice []T, fn func(T))
- func ParForEachErr[T any](ctx context.Context, slice []T, fn func(context.Context, T) error) error
- func ParGroupBy[T any, K comparable](ctx context.Context, slice []T, fn func(T) K) map[K][]T
- func ParGroupByErr[T any, K comparable](ctx context.Context, slice []T, fn func(context.Context, T) (K, error)) (map[K][]T, error)
- func ParMap[T, R any](ctx context.Context, slice []T, fn func(T) R) []R
- func ParMapErr[T, R any](ctx context.Context, slice []T, fn func(context.Context, T) (R, error)) ([]R, error)
- func Partition[T any](slice []T, pred func(T) bool) ([]T, []T)
- func Pascal(s string) string
- func PermitNil[T any](recipe T) T
- func Recover(errPtr ...*error)
- func Recv[T any](ch <-chan T) T
- func RecvAny[T any](chans ...<-chan T) T
- func RecvAnyCtx[T any](ctx context.Context, chans ...<-chan T) T
- func RecvAnyRaw[T any](chans ...<-chan T) (T, error)
- func RecvAnyRawCtx[T any](ctx context.Context, chans ...<-chan T) (T, error)
- func RecvCtx[T any](ctx context.Context, ch <-chan T) T
- func RecvRawCtx[T any](ctx context.Context, ch <-chan T) (T, error)
- func Reduce[T any](slice []T, fn func(T, T) T) T
- func Require(cond bool, msg ...string)
- func SlogAttr[T any](v T) slog.Attr
- func SlogContextHandler(base slog.Handler) slog.Handler
- func SlogCtx(ctx context.Context, attrs ...slog.Attr) context.Context
- func SlogFile() slog.Attr
- func SlogFileLine() slog.Attr
- func SlogLine() slog.Attr
- func Snake(s string) string
- func Sort[T cmp.Ordered](slice []T) []T
- func SortBy[T any, K cmp.Ordered](slice []T, fn func(T) K) []T
- func SortFunc[T any](slice []T, less func(a, b T) int) []T
- func Sum[T cmp.Ordered](slice []T) T
- func TODO(msg ...string)
- func Tag[T any](field, key string) string
- func Take[T any](slice []T, n int) []T
- func Tern[T any](cond bool, ifTrue, ifFalse T) T
- func Timeout(ctx context.Context, dur time.Duration) context.Context
- func Title(s string) string
- func ToErr[T any, E any, P interface{ ... }](v T, e P) (T, error)
- func Trace[T any](v T, err error) T
- func Try[T any](v T, err error) T
- func TypeName[T any]() string
- func Unreachable(msg ...string)
- func Unwrap[T any](v T, err error) T
- func Unzip[A, B any](pairs []Pair[A, B]) ([]A, []B)
- func Upper(s string) string
- func Values[K comparable, V any](m map[K]V) []V
- func WithAssemblyDebug(ctx context.Context) context.Context
- func WithAssemblyDebugWriter(ctx context.Context, w io.Writer) context.Context
- func WithPar(ctx context.Context, limit int) context.Context
- func WithParUnbounded(ctx context.Context) context.Context
- func Yield[T any](v T)
- func ZipMap[K comparable, V any](keys []K, values []V) map[K]V
- type AssemblyResult
- type Atom
- type CheckResult
- type Codec
- type ConvertOption
- type Coroutine
- type ErrResult
- func AwaitAllCtxE[T any](ctx context.Context, futures ...Future[T]) ErrResult[[]T]
- func AwaitAllE[T any](futures ...Future[T]) ErrResult[[]T]
- func AwaitAnyCtxE[T any](ctx context.Context, futures ...Future[T]) ErrResult[T]
- func AwaitAnyE[T any](futures ...Future[T]) ErrResult[T]
- func AwaitCtxE[T any](ctx context.Context, f Future[T]) ErrResult[T]
- func AwaitE[T any](f Future[T]) ErrResult[T]
- func DrainAllCtxE[T any](ctx context.Context, chans ...<-chan T) ErrResult[[]T]
- func DrainCtxE[T any](ctx context.Context, ch <-chan T) ErrResult[[]T]
- func RecvAnyCtxE[T any](ctx context.Context, chans ...<-chan T) ErrResult[T]
- func RecvAnyE[T any](chans ...<-chan T) ErrResult[T]
- func RecvCtxE[T any](ctx context.Context, ch <-chan T) ErrResult[T]
- func TryE[T any](v T, err error) ErrResult[T]
- func (r ErrResult[T]) Catch(fn func(error) (T, error)) T
- func (r ErrResult[T]) Err(replacement error) T
- func (r ErrResult[T]) ErrF(fn func(error) error) T
- func (r ErrResult[T]) RecoverAs(typedNil error, value T) ErrResult[T]
- func (r ErrResult[T]) RecoverIs(sentinel error, value T) ErrResult[T]
- func (r ErrResult[T]) Wrap(msg string) T
- func (r ErrResult[T]) Wrapf(format string, args ...any) T
- type FnParams
- type Future
- type GenMarker
- type LazyValue
- type LazyValueE
- type MatchArm
- type NilResult
- type OkResult
- type OneOf2
- type OneOf3
- type OneOf4
- type OneOf5
- type OneOf6
- type OpenResult
- type OpenResultE
- func (r OpenResultE[T]) Catch(fn func(error) (T, error)) OpenResultE[T]
- func (r OpenResultE[T]) DeferCleanup(cleanup ...any) T
- func (r OpenResultE[T]) Err(replacement error) OpenResultE[T]
- func (r OpenResultE[T]) ErrF(fn func(error) error) OpenResultE[T]
- func (r OpenResultE[T]) NoDeferCleanup() T
- func (r OpenResultE[T]) WithScope(args ...any) T
- func (r OpenResultE[T]) Wrap(msg string) OpenResultE[T]
- func (r OpenResultE[T]) Wrapf(format string, args ...any) OpenResultE[T]
- type Pair
- type PanicError
- type PathChain
- type RecoverResult
- type SQLQuery
- type Scope
- func (s *Scope) Attach(c interface{ ... }) error
- func (s *Scope) AttachE(c interface{ ... }) error
- func (s *Scope) AttachFn(handle any, cleanup func()) error
- func (s *Scope) AttachFnE(handle any, cleanup func() error) error
- func (s *Scope) BoundTo(ctx context.Context) *Scope
- func (s *Scope) Close()
- func (s *Scope) Closed() bool
- func (s *Scope) Commit(entries []ScopeEntry, child *Scope) error
- func (s *Scope) DeferCleanup() *Scope
- func (s *Scope) Detach(handle any) bool
- func (s *Scope) Load(key string) (any, bool)
- func (s *Scope) NoDeferCleanup() (*Scope, func())
- type ScopeEntry
- type TraceResult
- type UnwrapResult
- type ValidatedStruct
Constants ¶
This section is empty.
Variables ¶
var DebugWriter io.Writer = os.Stderr
DebugWriter is the destination q.DebugPrintlnAt writes to. Defaults to os.Stderr; tests and library users can reassign it to capture q.DebugPrintln output for assertions.
var ErrBadTypeAssert = errors.New("q: type assertion failed")
ErrBadTypeAssert is the sentinel error the bare q.As bubble produces when the type assertion's ok flag is false. Use q.AsE to supply a richer error.
var ErrChanClosed = errors.New("q: channel closed")
ErrChanClosed is the sentinel error the bare q.Recv bubble produces when the channel is closed (i.e. the receive's ok flag is false). Use q.RecvE to supply a richer error.
var ErrEnumUnknown = errors.New("q: unknown enum value")
ErrEnumUnknown is wrapped (via %w) into the bubble produced by q.EnumParse when the input string doesn't match any known constant of the target enum type. Callers can errors.Is the resulting error against this sentinel to identify the failure mode.
var ErrNil = errors.New("q: nil value")
ErrNil is the sentinel error the bare q.NotNil bubble produces when its supplied pointer is nil. Callers can errors.Is against it to detect "this came from q.NotNil specifically". Reach for q.NotNilE when a richer error is needed.
var ErrNotOk = errors.New("q: not ok")
ErrNotOk is the sentinel error the bare q.Ok bubble produces when its supplied ok flag is false. Mirrors ErrNil's role for the comma-ok family (map lookups, type assertions, channel receives). Reach for q.OkE when a richer error is needed.
var ErrRequireFailed = errors.New("q.Require failed")
ErrRequireFailed is wrapped (via %w) into the bubble produced by q.Require when its condition is false. Callers can errors.Is the resulting error against this sentinel to detect "this came from a q.Require call". The wrapping fmt.Errorf prefixes the call-site file:line and any user-supplied message before the sentinel.
var ErrScopeClosed = errors.New("q: scope closed")
ErrScopeClosed is returned by q.Assemble[T](...).WithScope(s) and by scope.Attach* / scope.Commit when s was closed before or during the call. Use errors.Is to detect across wrappings.
Functions ¶
func A ¶ added in v0.0.94
func A[T ~string]() T
A summons an instance of atom type T. The preprocessor rewrites each call site to T("<importPath>.<TypeName>") — a typed-string cast that folds at compile time. The runtime body is unreachable in a successful build.
Example:
type Pending q.Atom
if status == q.A[Pending]() {
// …
}
T's underlying type must be string (i.e. T = q.Atom, or T = string, or any type derived from one of those). The preprocessor and Go's own type checker enforce this together — q.A's `~string` constraint rejects non-string types, and the rewritten T("name") cast fails compilation if T isn't string-compatible.
func AllFields ¶ added in v0.0.44
AllFields returns the names of every field on T, including unexported ones, in source declaration order.
func As ¶ added in v0.0.15
As asserts x holds a T and forwards it; the preprocessor rewrites the call site into the inlined `v, _ok := x.(T); if !_ok { return zero, q.ErrBadTypeAssert }` shape. Use q.AsE to supply a richer error.
func AsOneOf ¶ added in v0.0.96
AsOneOf wraps v into a sum type T whose underlying type is one of the q.OneOfN forms. The preprocessor validates v's type matches one of T's arm type-arguments, then rewrites the call site to `T{Tag: <position>, Value: v}` — a composite literal with the right 1-based tag.
T must be a defined type whose underlying type is q.OneOfN[…]; `q.AsOneOf[q.OneOf2[A,B]](a)` (no named alias) also works.
Example:
type Status q.OneOf2[Pending, Done]
s := q.AsOneOf[Status](Done{...}) // → Status{Tag: 2, Value: Done{...}}
Build-time errors:
- T isn't a OneOfN-derived type → diagnostic.
- v's type isn't identical to any of T's arm types → diagnostic listing the accepted arms.
- T has duplicate arm types (e.g. q.OneOf2[int, int]) → diagnostic (the variant would be ambiguous).
func AssemblyDebugWriter ¶ added in v0.0.72
AssemblyDebugWriter returns the writer registered via WithAssemblyDebug or WithAssemblyDebugWriter, or nil when neither has been called on this ctx (or any of its ancestors). The rewritten q.Assemble IIFE consults it once per call to gate trace output: the conditional `if w := q.AssemblyDebugWriter(ctx); w != nil { ... }` adds one ctx.Value lookup to the no-debug path, which is microseconds.
func AtCompileTime ¶ added in v0.0.56
AtCompileTime evaluates fn at preprocessor time and splices the result as a value at the call site.
Restrictions enforced at compile time:
- The argument MUST be a `*ast.FuncLit` (an inline anonymous function literal). A function reference or variable holding a func value is rejected.
- The closure must have no captures from the enclosing scope, EXCEPT other q.AtCompileTime results — those are allowed and resolved in dependency order via topological sort.
- The closure must NOT call q.* (rewriter doesn't recurse into synthesized comptime programs).
- R must round-trip through the chosen codec.
The optional codec controls value transport. Default is JSONCodec[R](). Other built-ins: GobCodec[R](), BinaryCodec[R](). Users can pass any value implementing Codec[R].
func AtCompileTimeCode ¶ added in v0.0.56
AtCompileTimeCode evaluates fn at preprocessor time, takes the returned string as Go source code, parses it, and splices the parsed expression in place of the call site. This is the macro flavour of q.AtCompileTime — the closure returns CODE, not a value.
The closure's return value MUST be a Go expression source (not a statement, not a declaration). The expression's type must match R. Same restrictions as q.AtCompileTime apply (no captures except other AtCompileTime LHS bindings, no recursion if it would cycle, closure must be a *ast.FuncLit literal).
Example:
greet := q.AtCompileTimeCode[func(string) string](func() string {
return `func(name string) string { return "Hello, " + name }`
})
// Rewriter splices:
// greet := func(name string) string { return "Hello, " + name }
The generated source can only reference symbols / types / packages already in scope at the call site. Imports the macro needs but the user file lacks must be added explicitly by the user.
func Await ¶ added in v0.0.15
Await blocks on the Future and forwards the value; the preprocessor rewrites the call site into the inlined `v, err := q.AwaitRaw(f); if err != nil { return zero, err }` shape. Reach for q.AwaitE for chain-style custom error handling on the await's bubble.
func AwaitAll ¶ added in v0.0.20
AwaitAll waits for every future to succeed and returns their collected values in input order. The preprocessor rewrites the call site into `vs, err := q.AwaitAllRaw(futures...); if err != nil { return zero, err }`.
func AwaitAllCtx ¶ added in v0.0.20
AwaitAllCtx is AwaitAll with context cancellation; the rewriter emits q.AwaitAllRawCtx as the inner helper.
func AwaitAllRaw ¶ added in v0.0.20
AwaitAllRaw waits for every future to complete in parallel, then returns the collected values in the order the futures were passed. Bubbles the first error observed — either a future's own error — and returns (nil, err) immediately; remaining futures' goroutines keep running until they finish on their own (Go has no goroutine-kill).
Plain runtime function (NOT rewritten by the preprocessor), callable directly when the raw ([]T, error) tuple is wanted.
func AwaitAllRawCtx ¶ added in v0.0.20
AwaitAllRawCtx is AwaitAllRaw with context cancellation: if ctx fires before all futures complete, returns (nil, ctx.Err()) immediately (without aggregating the pending futures' Ctx-error duplicates). Same goroutine-leak caveat as AwaitRawCtx — thread ctx into each q.Async closure for true early cancellation of the spawned work.
func AwaitAny ¶ added in v0.0.20
AwaitAny returns the first future to succeed. If every future fails, bubbles an errors.Join of each failure. Preprocessor- rewritten like q.Await.
func AwaitAnyCtx ¶ added in v0.0.20
AwaitAnyCtx is AwaitAny with context cancellation.
func AwaitAnyRaw ¶ added in v0.0.20
AwaitAnyRaw returns the first future to complete successfully. If every future returns an error, returns (zero, errors.Join(…)) of each collected error in completion order.
Plain runtime function (NOT rewritten by the preprocessor).
func AwaitAnyRawCtx ¶ added in v0.0.20
AwaitAnyRawCtx is AwaitAnyRaw with context cancellation: if ctx fires before any success, returns (zero, ctx.Err()) once — any already-collected per-future errors are discarded in favour of ctx.Err().
func AwaitCtx ¶ added in v0.0.20
AwaitCtx blocks on a Future while honouring ctx cancellation. The preprocessor rewrites the call site into the inlined `v, err := q.AwaitRawCtx(ctx, f); if err != nil { return zero, err }` shape. Reach for q.AwaitCtxE for chain-style custom error handling.
func AwaitRaw ¶ added in v0.0.15
AwaitRaw blocks on the Future and returns its (T, error) result. Plain runtime function (not rewritten). The preprocessor rewrites q.Await / q.AwaitE internally into calls to AwaitRaw — user code may also call it directly when they want the raw tuple.
func AwaitRawCtx ¶ added in v0.0.20
AwaitRawCtx is the runtime helper for q.AwaitCtx. Blocks on the Future's result channel and ctx.Done(); returns the Future's (v, err) on completion or (zero, ctx.Err()) on cancellation. If ctx fires first, the underlying goroutine continues until fn returns on its own — Go has no goroutine-kill. Thread the same ctx into the q.Async closure when early cancellation of the spawned work is needed.
func Camel ¶ added in v0.0.42
Camel converts s to camelCase: first word lower, subsequent words upper-initial. Separators (_, -, space) are removed.
q.Camel("hello_world") // "helloWorld"
q.Camel("hello-world") // "helloWorld"
q.Camel("hello world") // "helloWorld"
q.Camel("HelloWorld") // "helloWorld"
func Check ¶ added in v0.0.6
func Check(err error)
Check bubbles err when non-nil. Use it in positions where the call being guarded returns only an error (file.Close, db.Ping, validate(...)). Reach for q.CheckE for chain-style custom error handling.
q.Check is always an expression statement — it returns nothing, so `v := q.Check(...)` and similar are rejected by the Go compiler.
Example:
func shutdown(conn *Conn) error {
q.Check(conn.Close())
q.Check(db.Ping())
return nil
}
func CheckCtx ¶ added in v0.0.126
CheckCtx is a context-cancellation checkpoint. Returns nothing — only valid as an expression statement. The preprocessor rewrites the call site into `if err := ctx.Err(); err != nil { return zero, err }`, bubbling either context.Canceled or context.DeadlineExceeded out of the enclosing function. Reach for q.CheckCtxE for chain-style error shaping around the bubble. See `docs/api/checkctx.md`.
func Chunk ¶ added in v0.0.47
Chunk groups slice into sub-slices of size n (the last may be shorter). Panics if n <= 0 — that's a programming error, not a recoverable runtime failure.
pages := q.Chunk(items, 50)
func Const ¶ added in v0.0.25
Const builds a Catch handler that always recovers to the supplied value, ignoring the captured error. Pure runtime helper — not rewritten by the preprocessor. Useful as the fallback in any chain method that takes a func(error) (T, error):
n := q.TryE(strconv.Atoi(s)).Catch(q.Const(0)) conn := q.OpenE(dial(addr)).Catch(q.Const(fallbackConn)).DeferCleanup((*Conn).Close)
q.Const fits the err-taking Catch shape used by ErrResult / OpenResultE / TraceResult / AwaitE chains. The no-arg-Catch shapes on q.NotNilE / q.OkE require a different signature; for those write the closure inline.
func ConvertTo ¶ added in v0.0.111
func ConvertTo[Target, Source any](src Source, opts ...ConvertOption) Target
ConvertTo produces a Target value populated from src's matching exported fields, with optional per-field overrides supplied via opts. Reads as: "Convert to Target from src".
type User struct { ID int; First, Last, Email string; Internal bool }
type UserDTO struct { ID int; FullName, Email, Source string }
dto := q.ConvertTo[UserDTO](user,
q.Set(UserDTO{}.Source, "v1"),
q.SetFn(UserDTO{}.Email, func(u User) string {
return strings.ToLower(u.Email)
}),
q.SetFn(UserDTO{}.FullName, func(u User) string {
return u.First + " " + u.Last
}),
)
The runtime body is unreachable in a successful build.
func ConvertToE ¶ added in v0.0.111
func ConvertToE[Target, Source any](src Source, opts ...ConvertOption) (Target, error)
ConvertToE is the fallible sibling of q.ConvertTo: it returns (Target, error) instead of just Target, so per-field overrides may fail and short-circuit the whole conversion. Use it when a field derivation calls something that can fail — fetching from a database, hitting a remote service, parsing an upstream blob — and you'd rather bubble the error than recover or panic.
dto, err := q.ConvertToE[UserDTO](u,
q.SetFnE(UserDTO{}.Email, func(u User) (string, error) {
return lookupEmail(ctx, u.ID)
}),
q.Set(UserDTO{}.Source, "v1"),
)
if err != nil { return err }
All standard overrides (q.Set, q.SetFn) work in q.ConvertToE too. The error path only fires when a q.SetFnE override returns a non-nil error; the first such error wins, in target-field declaration order. Pair with q.Try for the bubble-flat shape:
dto := q.Try(q.ConvertToE[UserDTO](u, q.SetFnE(...)))
q.SetFnE is rejected by the rewriter if used inside q.ConvertTo (no error slot to bubble to).
The runtime body is unreachable in a successful build.
func Count ¶ added in v0.0.47
Count returns the number of elements matching pred. Walks the whole slice; does not short-circuit (use q.Exists for that).
func Deadline ¶ added in v0.0.20
Deadline derives a child context cancelled at t. Same shape as Timeout but takes a time.Time for propagating an inherited deadline (e.g. from an HTTP header or parent job) rather than a fresh relative timeout.
func DebugPrintln ¶ added in v0.0.23
func DebugPrintln[T any](v T) T
DebugPrintln prints v to q.DebugWriter (defaults to os.Stderr) prefixed with the call-site file:line and the source text of the argument expression, then returns v unchanged so the call can sit mid-expression. Go's missing `dbg!` / `println!`. Usable anywhere a value expression is valid:
return q.DebugPrintln(loadUser(q.DebugPrintln(id)))
Both prints fire in source order, then the return flows through. Only the preprocessor knows the source text and file:line — without it, q.DebugPrintln is a panic stub. Every rewritten site calls q.DebugPrintlnAt internally, which is also exported for direct use when a custom label is wanted.
func DebugPrintlnAt ¶ added in v0.0.23
DebugPrintlnAt is the runtime half of q.DebugPrintln. The preprocessor rewrites every `q.DebugPrintln(x)` call site into `q.DebugPrintlnAt("<file>:<line> <src>", x)`, but users who want a custom label (or who need to construct the label at runtime) can call DebugPrintlnAt directly without going through the preprocessor path.
func DebugSlogAttr ¶ added in v0.0.23
DebugSlogAttr returns a slog.Attr keyed by the call-site `<file>:<line> <src>` label captured at compile time, with v as the value. Use it to attach a labelled value to a structured-log call without retyping the source expression as a key:
slog.Info("loaded", q.DebugSlogAttr(userID))
// → slog.Info("loaded", slog.Any("main.go:42 userID", userID))
The preprocessor rewrites every q.DebugSlogAttr call site into the equivalent slog.Any expression at compile time. There is no runtime helper for this one — the rewrite expands directly to stdlib slog, so the value path is purely a stdlib slog call.
Unlike q.DebugPrintln, q.DebugSlogAttr does not pass v through: it returns a slog.Attr suitable for the variadic args of slog.Info / slog.Error / etc. For mid-expression instrumentation reach for q.DebugPrintln.
func Distinct ¶ added in v0.0.47
func Distinct[T comparable](slice []T) []T
Distinct returns each unique element preserving first-occurrence order. T must be comparable (uses a map for O(n) deduplication).
Different from `slices.Compact`: that only collapses *adjacent* equal elements, so you'd typically sort first (which mutates element order). Distinct is the order-preserving "Scala-style distinct" people usually reach for.
func DistinctBy ¶ added in v0.0.78
func DistinctBy[T any, K comparable](slice []T, fn func(T) K) []T
DistinctBy returns each element whose key (computed via fn) is seen first, preserving input order. Useful when T isn't comparable, or when you want dedup by some derived attribute (case-insensitive strings, just one field of a struct, etc.).
emails := q.DistinctBy(rawEmails, strings.ToLower) // case-insensitive dedup
users := q.DistinctBy(users, func(u User) int { return u.ID })
The key fn runs once per input element. Only the first element per key is kept.
func Drain ¶ added in v0.0.20
func Drain[T any](ch <-chan T) []T
Drain receives from ch until it closes, returning the collected values in reception order. Plain runtime function — no error path (the only way to fail is ctx cancellation, and this form doesn't take one). If ch never closes, Drain blocks forever; reach for q.DrainCtx when a cancellable wait is needed.
func DrainAll ¶ added in v0.0.20
func DrainAll[T any](chans ...<-chan T) [][]T
DrainAll drains every supplied channel concurrently until all close, returning the per-channel collected values in input order. Plain runtime function — same no-error-path semantics as Drain.
func DrainAllCtx ¶ added in v0.0.20
DrainAllCtx drains every channel until all close or ctx cancels. Preprocessor-rewritten as Try-like bubble over q.DrainAllRawCtx.
func DrainAllRawCtx ¶ added in v0.0.20
DrainAllRawCtx drains every supplied channel concurrently until all close or ctx cancels. On cancel, returns (nil, ctx.Err()) — partial per-channel results are discarded on the bubble path. Background goroutines continue draining until each source closes (Go has no goroutine-kill); thread ctx into the producer side for true early shutdown.
func DrainCtx ¶ added in v0.0.20
DrainCtx drains ch until close or ctx cancellation. Preprocessor- rewritten as Try-like bubble over q.DrainRawCtx. Bubbles ctx.Err() on cancel.
func DrainRawCtx ¶ added in v0.0.20
DrainRawCtx receives from ch until it closes or ctx cancels. On cancel, returns (nil, ctx.Err()) — the already-gathered values are discarded on the bubble path.
func Drop ¶ added in v0.0.47
Drop returns slice with the first n elements removed (or empty if n exceeds len(slice)). Negative n is treated as 0.
func EnumName ¶ added in v0.0.35
func EnumName[T comparable](v T) string
EnumName returns the identifier name corresponding to v, or "" when v is not a known constant of T. Rewritten to an inline switch expression.
q.EnumName[Color](Green) // "Green"
func EnumNames ¶ added in v0.0.35
func EnumNames[T comparable]() []string
EnumNames returns the identifier names of every constant of type T, in source declaration order. Rewritten to a literal slice of strings.
names := q.EnumNames[Color]() // []string{"Red", "Green", "Blue"}
func EnumOrdinal ¶ added in v0.0.35
func EnumOrdinal[T comparable](v T) int
EnumOrdinal returns v's 0-based position among T's constants in declaration order, or -1 when v is not a known constant. Rewritten to an inline switch expression.
q.EnumOrdinal[Color](Green) // 1
func EnumParse ¶ added in v0.0.35
func EnumParse[T comparable](s string) (T, error)
EnumParse converts s into the corresponding T constant, or (zero, q.ErrEnumUnknown wrapped with the input) when s names no constant of T. Rewritten to an inline switch expression.
c, err := q.EnumParse[Color]("Green") // Green, nil
_, err = q.EnumParse[Color]("Pink") // errors.Is(err, q.ErrEnumUnknown) == true
func EnumValid ¶ added in v0.0.35
func EnumValid[T comparable](v T) bool
EnumValid reports whether v matches one of T's constants. Rewritten to an inline switch expression.
q.EnumValid[Color](Green) // true q.EnumValid[Color](Color(99)) // false
func EnumValues ¶ added in v0.0.35
func EnumValues[T comparable]() []T
EnumValues returns every constant of type T declared in T's package, in source declaration order. Rewritten to a literal slice expression at compile time.
Example:
type Color int
const (Red Color = iota; Green; Blue)
colors := q.EnumValues[Color]() // []Color{Red, Green, Blue}
func Exhaustive ¶ added in v0.0.39
func Exhaustive[T any](v T) T
Exhaustive marks a `switch` as exhaustively covering every constant of T. The preprocessor recognises the shape
switch q.Exhaustive(v) {
case A: …
case B: …
}
at compile time: if any constant of v's defined type is missing from the case clauses, the build fails with a diagnostic naming the missing constants.
A `default:` clause does NOT replace coverage of the declared constants — it catches values outside the declared set (forward-compat with Lax-JSON-opted types, wire drift, or future constants a downstream service hasn't adopted yet). Every declared constant still needs its own case; default is additive and recommended for any type that can carry unknown values.
The wrapper is removed at rewrite time, so the runtime code is a plain `switch v { … }`. Legal only as the tag of a switch statement; any other position is a build error.
Anything declared via `const … T = …` in T's home package counts as a constant. Cross-package T is rejected (the rewriter would otherwise need to write qualified case names — declare a thin wrapper in the enum's home package).
func Exists ¶ added in v0.0.47
Exists reports whether any element satisfies pred. Short-circuits on the first match (Scala's `exists`, samber/lo's `SomeBy`).
func ExistsErr ¶ added in v0.0.47
ExistsErr is Exists with a fallible predicate. First error short-circuits ahead of any "found" decision.
func Expr ¶ added in v0.0.29
Expr returns the literal source text of its argument as a string, captured at compile time. The argument is type-checked (so it must be valid Go) but its runtime value is discarded:
q.Expr(a + b) // → "a + b" q.Expr(user.Email) // → "user.Email" q.Expr(items[i*2]) // → "items[i*2]"
Useful for self-documenting error messages or labels that reflect the exact source spelling of an expression. The type parameter is `any`, so any expression form is accepted.
func F ¶ added in v0.0.35
F builds a formatted string by interpolating `{expr}` segments at compile time. Each placeholder is replaced with the formatted value of the Go expression `expr`, evaluated in the caller's scope.
name := "world"
q.F("hello {name}, you are {age+1}")
// → fmt.Sprintf("hello %v, you are %v", name, age+1)
The format must be a Go string literal — passing a runtime string surfaces a diagnostic. For runtime-built formats, use fmt.Sprintf directly.
func Ferr ¶ added in v0.0.35
Ferr is `errors.New(q.F(format))` shaped — useful when the interpolation result is the immediate error.
return q.Ferr("user {id} not found")
// → errors.New(fmt.Sprintf("user %v not found", id))
func Fields ¶ added in v0.0.44
Fields returns the names of T's EXPORTED fields in source declaration order. T must be a struct type (or *struct). Embedded fields are returned by their declared name (the type's unqualified identifier).
type User struct {
ID int `json:"id"`
Name string `json:"name"`
pwd string // unexported — excluded
}
q.Fields[User]() // []string{"ID", "Name"}
func File ¶ added in v0.0.29
func File() string
File returns the basename of the call-site source file as a plain string (e.g. "main.go"). Captured at compile time. Use this when you want the location info as a primitive value rather than a slog.Attr.
func FileLine ¶ added in v0.0.29
func FileLine() string
FileLine returns "<basename>:<line>" as a plain string, e.g. "main.go:42". Captured at compile time.
func Filter ¶ added in v0.0.47
Filter returns the elements for which pred returns true, in input order. Allocates a new slice; the input is not mutated.
active := q.Filter(users, func(u User) bool { return u.Active })
func FilterErr ¶ added in v0.0.47
FilterErr is Filter with a fallible predicate. First error short-circuits.
func Find ¶ added in v0.0.47
Find returns the first element satisfying pred, with ok=true; or (zero, false) if no element matches. Pairs naturally with q.Ok / q.OkE for bubble-on-not-found shapes:
user := q.Ok(q.Find(users, isAdmin))
user := q.OkE(q.Find(users, isAdmin)).Wrap("no admin user")
func FlatMap ¶ added in v0.0.47
func FlatMap[T, R any](slice []T, fn func(T) []R) []R
FlatMap applies fn to each element and concatenates the per-element slices into a single output slice (in input order).
pairs := q.FlatMap(items, func(it Item) []Pair { return it.Pairs })
func FlatMapErr ¶ added in v0.0.47
FlatMapErr is FlatMap with a fallible fn. First error short-circuits.
func Fln ¶ added in v0.0.35
func Fln(format string)
Fln writes the interpolated string + "\n" to q.DebugWriter (defaults to os.Stderr). Useful for ad-hoc diagnostics that don't warrant a full slog setup.
q.Fln("processing {len(items)} items for {user.Name}")
func Fold ¶ added in v0.0.47
func Fold[T, R any](slice []T, init R, fn func(R, T) R) R
Fold folds slice with init via fn (fold-left). The accumulator type R may differ from the element type T. When slice is empty, returns init unchanged. Scala's `foldLeft` shape.
sum := q.Fold(nums, 0, func(acc, n int) int { return acc + n })
csv := q.Fold(items, "", func(acc string, it item) string {
if acc == "" { return it.Name }
return acc + "," + it.Name
})
func FoldErr ¶ added in v0.0.47
FoldErr is Fold with a fallible step fn. First error short-circuits — the partial accumulator is not returned (use the bubble path for "errored after partial work").
func ForAll ¶ added in v0.0.47
ForAll reports whether every element satisfies pred. Short-circuits on the first miss (Scala's `forall`, samber/lo's `EveryBy`). Vacuously true on an empty slice.
func ForAllErr ¶ added in v0.0.47
ForAllErr is ForAll with a fallible predicate. First error short-circuits ahead of any "all match" decision.
func ForEach ¶ added in v0.0.47
func ForEach[T any](slice []T, fn func(T))
ForEach iterates slice and calls fn for each element, in input order. Side-effect-only — no result collected. Almost identical to a plain `for _, v := range slice { fn(v) }`; the helper exists so "swap to parallel" is a one-line change to q.ParForEach without restructuring the loop:
q.ForEach(items, func(it Item) { log(it) })
q.ParForEach(ctx, items, func(it Item) { log(it) }) // parallel
func ForEachErr ¶ added in v0.0.47
ForEachErr iterates slice and calls fn for each element, in input order. First error short-circuits and is returned; subsequent elements are not visited. Compose with q.Check / q.CheckE for the bubble path:
q.Check(q.ForEachErr(rows, validateRow))
func Generator ¶ added in v0.0.63
Generator turns a body that calls q.Yield(v) into a stdlib iter.Seq[T]. The preprocessor rewrites the call site, transforming the body into the iter.Seq callback shape: every q.Yield(v) becomes `if !yield(v) { return }`, and the whole expression is converted to `iter.Seq[T](func(yield func(T) bool) { ... })`.
The type parameter T is required at the call site (Go can't infer generic type arguments that appear only in the result type).
Example:
fibs := q.Generator[int](func() {
a, b := 0, 1
for {
q.Yield(a)
a, b = b, a+b
}
})
for v := range fibs {
if v > 100 { break }
fmt.Println(v)
}
Reach for q.Coro when the body needs to receive values from the caller as well as emit them.
Inside a Generator body, `return` exits the body (ending the sequence). The yield function is hidden by the rewriter — call q.Yield(v) instead of writing the boilerplate `if !yield(v) { return }` form. q.Yield is recognised only inside a Generator's body; outside it, the runtime stub panics.
Nested closures inside the body: q.Yield calls in nested closures are also rewritten, but the early `return` exits the innermost enclosing func. This matches the behaviour of writing the iter.Seq callback by hand.
func GetPar ¶ added in v0.0.47
GetPar reads the worker-count limit from ctx (set by q.WithPar / q.WithParUnbounded). Returns runtime.NumCPU() when no limit is set; returns -1 (parUnbounded) when q.WithParUnbounded was used.
func GoroutineID ¶ added in v0.0.31
func GoroutineID() uint64
GoroutineID returns the current goroutine's runtime ID — the integer shown in panic stack traces ("goroutine 17 [running]:") and the goroutine pprof profile. Type matches runtime.g.goid (uint64). Stable for the goroutine's lifetime.
func GroupBy ¶ added in v0.0.47
func GroupBy[T any, K comparable](slice []T, fn func(T) K) map[K][]T
GroupBy buckets each element by the key fn returns. Bucket order within a group preserves input order. The result map is freshly allocated.
byCat := q.GroupBy(items, func(it Item) string { return it.Category })
func InstallSlog ¶ added in v0.0.30
InstallSlog wraps base with q.SlogContextHandler and installs the result as slog's default logger (slog.SetDefault). After this call, any package-level slog.InfoContext / ErrorContext / etc. will pick up attrs added to the ctx via q.SlogCtx.
Call once at process startup. The underlying base handler remains in your control — q just adds the ctx-attr lookup on top.
func InstallSlogJSON ¶ added in v0.0.30
func InstallSlogJSON(w io.Writer, opts *slog.HandlerOptions)
InstallSlogJSON is sugar for InstallSlog(slog.NewJSONHandler(w, opts)). Both arguments may be nil — w defaults to os.Stderr, opts to stdlib defaults.
func InstallSlogText ¶ added in v0.0.30
func InstallSlogText(w io.Writer, opts *slog.HandlerOptions)
InstallSlogText is sugar for InstallSlog(slog.NewTextHandler(w, opts)). Both arguments may be nil — w defaults to os.Stderr, opts to stdlib defaults.
func Kebab ¶ added in v0.0.42
Kebab converts s to kebab-case. Separators are dashes.
q.Kebab("HelloWorld") // "hello-world"
q.Kebab("XMLHttpRequest") // "xml-http-request"
q.Kebab("hello_world") // "hello-world"
func Keys ¶ added in v0.0.50
func Keys[K comparable, V any](m map[K]V) []K
Keys returns a slice of m's keys in unspecified order. Thin wrapper over `slices.Collect(maps.Keys(m))` — saves the import + the two-step incantation at the call site.
func Line ¶ added in v0.0.29
func Line() int
Line returns the integer line number of the call site as a plain int. Captured at compile time.
func Lock ¶ added in v0.0.15
Lock acquires l and registers a deferred Unlock in the enclosing function. Always an expression statement — returns nothing. Accepts any sync.Locker (*sync.Mutex, *sync.RWMutex for the write side, rwm.RLocker() for the read side, user-defined types).
Example:
func (s *store) Set(k, v string) {
q.Lock(&s.mu)
s.data[k] = v
}
func LogCloseErr ¶ added in v0.0.80
LogCloseErr is the auto-cleanup error sink used by q.Assemble's auto-detected resource cleanups when T's Close() returns an error. Surfacing failed teardown via slog.Error means a flaky shutdown is loud rather than silent.
Users with a custom logging story can replace q.LogCloseErr by shadowing it in their own package — but the more idiomatic approach is to write an explicit `(T, func(), error)` recipe that handles the close in whatever way fits.
func Lower ¶ added in v0.0.42
Lower returns format with all letters mapped to lower case (ASCII).
q.Lower("HELLO") // "hello"
func Map ¶ added in v0.0.47
func Map[T, R any](slice []T, fn func(T) R) []R
Map applies fn to each element of slice and returns the collected results in input order. Output length always equals input length.
doubled := q.Map(nums, func(n int) int { return n * 2 })
func MapEntries ¶ added in v0.0.50
func MapEntries[K1, K2 comparable, V1, V2 any](m map[K1]V1, fn func(K1, V1) (K2, V2)) map[K2]V2
MapEntries transforms each (key, value) pair of m via fn, producing a new map with possibly different K2 / V2 types. The one-pass form of MapKeys + MapValues when both transformations depend on each other or on the original entry.
type alias struct{ name string; v int }
canonical := q.MapEntries(byID, func(id int, a alias) (string, int) {
return strings.ToLower(a.name), a.v
})
Same collision caveat as MapKeys: if two source pairs produce the same K2, last-write-wins (and "last" is undefined per Go's map-iteration semantics).
func MapEntriesErr ¶ added in v0.0.50
func MapEntriesErr[K1, K2 comparable, V1, V2 any](m map[K1]V1, fn func(K1, V1) (K2, V2, error)) (map[K2]V2, error)
MapEntriesErr is MapEntries with a fallible fn (returns `(K2, V2, error)`). First error short-circuits.
func MapErr ¶ added in v0.0.47
MapErr is Map with a fallible fn. Returns (results, nil) on full success or (nil, err) on the first failure — remaining elements are not visited. Compose with q.Try / q.TryE for the bubble path:
users := q.Try(q.MapErr(rows, parseUser))
func MapKeys ¶ added in v0.0.50
func MapKeys[K1, K2 comparable, V any](m map[K1]V, fn func(K1) K2) map[K2]V
MapKeys transforms each key of m via fn, preserving values. If fn produces collisions (two K1 keys mapping to the same K2 key), last-write-wins by Go's map-iteration semantics — the iteration order over m is undefined, so the surviving value is also undefined. Avoid collisions if it matters.
Allocates a fresh map; the input is not mutated.
func MapKeysErr ¶ added in v0.0.50
func MapKeysErr[K1, K2 comparable, V any](m map[K1]V, fn func(K1) (K2, error)) (map[K2]V, error)
MapKeysErr is MapKeys with a fallible fn. First error short-circuits.
func MapValues ¶ added in v0.0.50
func MapValues[K comparable, V1, V2 any](m map[K]V1, fn func(V1) V2) map[K]V2
MapValues transforms each value of m via fn, preserving keys. Pairs naturally with q.GroupBy for "group then aggregate" pipelines:
counts := q.MapValues(q.GroupBy(items, byCat),
func(group []Item) int { return len(group) })
sums := q.MapValues(q.GroupBy(items, byCat),
func(g []Item) int { return q.Fold(g, 0, addAmount) })
Allocates a fresh map; the input is not mutated.
func MapValuesErr ¶ added in v0.0.50
func MapValuesErr[K comparable, V1, V2 any](m map[K]V1, fn func(V1) (V2, error)) (map[K]V2, error)
MapValuesErr is MapValues with a fallible fn. First error short-circuits and is returned; the partial map is discarded. Iteration order over m is map-random — there is no notion of "first error in input order" because Go's range over a map doesn't define one.
func Match ¶ added in v0.0.45
Match folds to a value-returning switch (or if/else-if chain when any arm is a predicate). value is dispatched against each q.Case's first argument; the matching arm's result is returned. q.Default catches anything not covered.
value is typed `any` — the preprocessor recovers the actual type via go/types and validates each arm's cond against it. Outside the preprocessor, `any` typing means q.Match is freely callable from Go's typechecker (you can pass a value of any type), with mistakes surfacing as q-pass diagnostics rather than gopls errors.
func Max ¶ added in v0.0.53
Max returns the largest element of slice. Empty input returns (zero, false).
func MaxBy ¶ added in v0.0.53
MaxBy returns the element maximising fn(elem). Empty input returns (zero, false). Ties go to the first occurrence.
func Min ¶ added in v0.0.53
Min returns the smallest element of slice. Empty input returns (zero, false) — pairs naturally with q.Ok / q.OkE for the bubble path.
func MinBy ¶ added in v0.0.53
MinBy returns the element minimising fn(elem). Empty input returns (zero, false). Ties go to the first occurrence.
func NotNil ¶
func NotNil[T any](p *T) *T
NotNil forwards p when non-nil; otherwise the preprocessor rewrites the call site into the inlined `if p == nil { return zero, q.ErrNil }` shape. Reach for q.NotNilE to provide a richer error.
func Ok ¶ added in v0.0.15
Ok forwards v when ok is true; the preprocessor rewrites the call site into the inlined `if !ok { return zero, q.ErrNotOk }` shape. Use Ok for comma-ok patterns: map lookups, type assertions, channel receives. Reach for q.OkE to provide a richer error.
Example:
func findUser(id int) (User, error) {
user := q.Ok(users[id]) // map lookup (v, ok)
admin := q.Ok(user.(Admin)) // type assertion (v, ok)
return admin, nil
}
func ParExists ¶ added in v0.0.47
ParExists reports whether any element satisfies pred — the parallel counterpart of q.Exists. Workers run in parallel; the first match causes the rest to wind down (subsequent dispatches see the sentinel and bail). Useful for IO-bound predicates ("does ANY of these URLs return 200?") where serial Exists would be slow.
ctx is read for the limit; cancellation is honoured (cancellation produces false — workers are torn down without resolving the question). For the cancellation-aware shape that surfaces the reason, use ParExistsErr.
func ParExistsErr ¶ added in v0.0.47
func ParExistsErr[T any](ctx context.Context, slice []T, pred func(context.Context, T) (bool, error)) (bool, error)
ParExistsErr is ParExists with a fallible pred. Returns (found bool, err error). The first true wins; the first non-nil err short-circuits with that err. ctx cancellation produces (false, ctx.Err()).
func ParFilter ¶ added in v0.0.47
ParFilter applies pred to each element in parallel and returns the matching elements in input order. Useful for IO-bound predicates (fetch-and-filter); for cheap predicates the sequential q.Filter is faster (no goroutine overhead).
ctx cancellation stops dispatch; in-flight workers run to completion. Un-dispatched indices stay false in the mask, so the returned slice is naturally a partial filter result. Check ctx.Err() after the call to distinguish cancel from "no matches."
func ParFilterErr ¶ added in v0.0.47
func ParFilterErr[T any](ctx context.Context, slice []T, pred func(context.Context, T) (bool, error)) ([]T, error)
ParFilterErr is ParFilter with a fallible predicate.
func ParFlatMap ¶ added in v0.0.47
ParFlatMap applies fn to each element in parallel, then concatenates the per-element slices in input order.
func ParFlatMapErr ¶ added in v0.0.47
func ParFlatMapErr[T, R any](ctx context.Context, slice []T, fn func(context.Context, T) ([]R, error)) ([]R, error)
ParFlatMapErr is ParFlatMap with a fallible fn.
func ParForAll ¶ added in v0.0.47
ParForAll reports whether every element satisfies pred — the parallel counterpart of q.ForAll. Vacuously true on an empty slice. Workers run in parallel; the first non-match causes the rest to wind down. Useful for IO-bound predicates ("do ALL of these URLs return 200?") where serial ForAll would be slow.
ctx cancellation produces false (the question can't be resolved without all elements). For the cancellation-aware shape, use ParForAllErr.
func ParForAllErr ¶ added in v0.0.47
func ParForAllErr[T any](ctx context.Context, slice []T, pred func(context.Context, T) (bool, error)) (bool, error)
ParForAllErr is ParForAll with a fallible pred. Returns (allMatch bool, err error). First non-match wins (returns (false, nil)); first non-nil err wins (returns (false, err)). ctx cancellation produces (false, ctx.Err()).
func ParForEach ¶ added in v0.0.47
ParForEach runs fn on every element in parallel; no result is collected. The fan-out form of "do X to each item, ignore the values." Symmetric with the sequential q.ForEach — swap to parallel by adding the Par prefix and a ctx.
ctx cancellation stops dispatch; in-flight workers run to completion. There's no return path to bubble cancel — callers who care should check ctx.Err() after the call.
func ParForEachErr ¶ added in v0.0.47
ParForEachErr is ParForEach with a fallible fn. First error wins and stops further scheduling. Compose with q.Check / q.CheckE for the bubble path:
q.Check(q.ParForEachErr(ctx, urls, postURL))
func ParGroupBy ¶ added in v0.0.48
func ParGroupBy[T any, K comparable](ctx context.Context, slice []T, fn func(T) K) map[K][]T
ParGroupBy buckets each element by the key fn returns, computing keys in parallel. The reassembly into the result map is sequential (map appends are fast — only the key fn benefits from parallelism, and only when fn is IO-bound or CPU-heavy enough to outweigh goroutine overhead).
On ctx cancellation, returns nil — partial keys would mis-group the elements they belong to. Callers who care about the cancel case should check ctx.Err() after the call.
func ParGroupByErr ¶ added in v0.0.48
func ParGroupByErr[T any, K comparable](ctx context.Context, slice []T, fn func(context.Context, T) (K, error)) (map[K][]T, error)
ParGroupByErr is ParGroupBy with a fallible key fn. First error short-circuits and returns (nil, err). ctx cancellation produces (nil, ctx.Err()).
func ParMap ¶ added in v0.0.47
ParMap applies fn to each element in parallel and returns the collected results in input order. fn cannot fail; for the fallible variant use q.ParMapErr.
ctx is read for the worker-count limit (see q.WithPar) AND for cancellation: when ctx fires, dispatch stops immediately and in-flight workers run to completion (Go has no goroutine kill). The returned slice is the partial set — indices that were never dispatched hold the zero value of R. Callers who care about the distinction should check ctx.Err() after the call.
func ParMapErr ¶ added in v0.0.47
func ParMapErr[T, R any](ctx context.Context, slice []T, fn func(context.Context, T) (R, error)) ([]R, error)
ParMapErr is ParMap with a fallible fn. First error wins (returned directly) and stops scheduling further work; in-flight workers continue running but their results / errors are discarded. ctx cancellation produces the same first-bubble-then-stop behaviour (returns ctx.Err()).
results := q.Try(q.ParMapErr(ctx, urls, fetchURL))
func Partition ¶ added in v0.0.47
Partition splits slice into (matching, nonMatching) by pred. Both slices preserve input order. Allocates two new slices.
func Pascal ¶ added in v0.0.42
Pascal converts s to PascalCase: every word upper-initial, separators removed.
q.Pascal("hello_world") // "HelloWorld"
q.Pascal("hello-world") // "HelloWorld"
q.Pascal("helloWorld") // "HelloWorld"
func PermitNil ¶ added in v0.0.83
func PermitNil[T any](recipe T) T
PermitNil wraps an Assemble recipe to opt the recipe out of the runtime nil-check the rewriter would otherwise emit on the recipe's bound _qDep<N> value. Use it when nil IS a valid output of the recipe — for example, an optional-dependency ctor where downstream consumers are written to handle a nil input.
// newOptionalCache may legitimately return nil ("no cache configured")
cache, err := q.Assemble[*Cache](newConfig, q.PermitNil(newOptionalCache)).DeferCleanup()
PermitNil is a typed identity at runtime — outside the preprocessor it just returns recipe unchanged. The preprocessor detects q.PermitNil(<recipe>) at scan time, unwraps to <recipe>, and marks the resulting Assemble step so the nil-check is skipped. Works with both function-reference and inline-value recipes.
PermitNil affects ONLY the nil-check on the recipe's own output. A nil input to a downstream consumer recipe doesn't change the consumer's behaviour — that's the consumer's problem. Recipes whose output type can't hold nil (struct values, basic types, arrays) pass through unchanged: there's no nil-check to skip.
func Recover ¶ added in v0.0.15
func Recover(errPtr ...*error)
Recover is the runtime helper paired with `defer q.Recover(&err)`. When a panic is in flight at defer-time, the recovered value and a debug.Stack() snapshot are wrapped in *PanicError and stored via errPtr. Plain runtime function — Go's recover() sees the panic because Recover IS the deferred function. Calling Recover without defer is a no-op.
Two call shapes are accepted:
- `defer q.Recover(&err)` — explicit form, pure runtime.
- `defer q.Recover()` — zero-arg auto form. The preprocessor rewrites the call to pass `&err` from the enclosing function's error-slot automatically, and — when that slot is unnamed — injects a named return on the signature. The enclosing function must have the built-in `error` as its last return.
Example (auto form):
func doWork() error { // becomes `(_qErr error)` post-rewrite
defer q.Recover()
riskyPanics()
return nil
}
func Recv ¶ added in v0.0.15
func Recv[T any](ch <-chan T) T
Recv receives from ch and forwards the value; the preprocessor rewrites the call site into the inlined `v, _ok := <-ch; if !_ok { return zero, q.ErrChanClosed }` shape. Use q.RecvE to supply a richer error.
func RecvAny ¶ added in v0.0.20
func RecvAny[T any](chans ...<-chan T) T
RecvAny returns the first value received across the supplied channels. Preprocessor-rewritten as Try-like bubble over q.RecvAnyRaw; reach for q.RecvAnyCtx when ctx cancellation should bail early, q.RecvAnyE / q.RecvAnyCtxE for chain-style error shaping (including "ignore close, keep waiting" via Catch+recover).
func RecvAnyCtx ¶ added in v0.0.20
RecvAnyCtx is RecvAny with ctx cancellation.
func RecvAnyRaw ¶ added in v0.0.20
RecvAnyRaw performs a dynamic N-way select over the supplied channels and returns the first value received. On any channel close, returns (zero, ErrChanClosed). If len(chans) == 0, returns a descriptive error (a 0-way select would block forever).
Uses reflect.Select under the hood — necessary because the channel count is runtime-sized. Plain runtime function (NOT rewritten by the preprocessor).
func RecvAnyRawCtx ¶ added in v0.0.20
RecvAnyRawCtx is RecvAnyRaw with ctx cancellation. If ctx fires before any channel delivers, returns (zero, ctx.Err()).
func RecvCtx ¶ added in v0.0.20
RecvCtx receives from ch while honouring ctx cancellation. The preprocessor rewrites the call site into `v, err := q.RecvRawCtx(ctx, ch); if err != nil { return zero, err }`. Use q.RecvCtxE to shape the bubbled error with the ErrResult vocabulary.
func RecvRawCtx ¶ added in v0.0.20
RecvRawCtx is the runtime helper for q.RecvCtx. select-blocks on ch and ctx.Done(); returns (v, nil) on a successful receive, (zero, ErrChanClosed) on a closed channel, and (zero, ctx.Err()) on context cancellation. Plain runtime function — callable directly when the raw tuple is wanted.
func Reduce ¶ added in v0.0.47
func Reduce[T any](slice []T, fn func(T, T) T) T
Reduce collapses slice into a single element using fn. The accumulator starts as the first element; fn is called for each subsequent element. T-only — both inputs and output share the element type. On empty input returns the zero value of T (no panic, no error sentinel) — Scala's `reduceLeft` panics; q's version leans on Go's zero-value default.
sum := q.Reduce(nums, func(a, b int) int { return a + b })
first := q.Reduce(items, func(a, _ Item) Item { return a })
Caveat: when fn is non-monoidal — i.e. `fn(zero, x) != x` — the empty-input result is mathematically meaningless: it's zero, not "no result". For max/min/multiply and similar, distinguish empty up front (`if len(slice) == 0`) or reach for q.Fold with an explicit identity:
mx := q.Fold(nums, math.MinInt, func(a, b int) int {
if a > b { return a }
return b
})
func Require ¶ added in v0.0.22
Require returns when cond is true; otherwise the preprocessor rewrites the call site to bubble an error of the form
errors.New("q.Require failed <file>:<line>[: <msg>]")
to the enclosing function's error return. Always an expression statement. Use for runtime preconditions where reporting via error is preferable to crashing the process — q's stance is that the library's job is returning errors, not generating panics.
func SlogAttr ¶ added in v0.0.28
SlogAttr returns a slog.Attr keyed by the source text of v (captured at compile time), with v as the value. Use it to attach a labelled value to a structured-log call without retyping the variable name as the slog key:
slog.Info("loaded", q.SlogAttr(userID))
// → slog.Info("loaded", slog.Any("userID", userID))
Unlike q.DebugSlogAttr, q.SlogAttr does NOT include the call-site file:line in the key — it's the production-grade slog helper for attaching named values, and a clean key matches typical slog output expectations. Pair with q.SlogFile / q.SlogLine when you want location info as separate attrs.
The preprocessor rewrites every q.SlogAttr call site directly to slog.Any at compile time; no q runtime helper sits on the value path. The log/slog import is auto-injected when this family appears.
func SlogContextHandler ¶ added in v0.0.30
SlogContextHandler wraps base so that every Handle call adds the slog.Attrs accumulated on the request context (via q.SlogCtx) to the record before forwarding. When the context carries no q-attached attrs, the handler is a transparent pass-through — equivalent to using base directly.
Stack with whatever handler-level wrapping you need (sampling, async, redaction, etc.) — q.SlogContextHandler is just one more slog.Handler in the chain.
func SlogCtx ¶ added in v0.0.30
SlogCtx returns a child context with the supplied slog.Attrs attached. Any handler produced by q.SlogContextHandler (and any logger using it as its handler) will pick these up via the passed context on slog.InfoContext / ErrorContext / etc.
Repeated calls accumulate: the returned ctx carries every attr from prior calls plus the new ones in source order. If attrs is empty the input ctx is returned unchanged.
func SlogFile ¶ added in v0.0.28
SlogFile returns a slog.Attr with key "file" and the basename of the call site's source file as value (e.g. "main.go"). Captured at compile time. Pair with q.SlogLine for "log this with where it was emitted" without writing the location info by hand:
slog.Info("processed", q.SlogFile(), q.SlogLine())
// → slog.Info("processed", slog.Any("file", "main.go"), slog.Any("line", 42))
Production-friendly counterpart to runtime.Caller / debug.Stack: the location is constant per call site, evaluated once at compile time, and shows up as ordinary slog attrs in your log output.
func SlogFileLine ¶ added in v0.0.29
SlogFileLine returns a slog.Attr with key "file" and a value of the form "<basename>:<line>" — the same compile-time capture as q.SlogFile + q.SlogLine combined into one attr. Use it when you want a single, parseable location string per log record:
slog.Info("event", q.SlogFileLine())
// → slog.Info("event", slog.Any("file", "main.go:42"))
func SlogLine ¶ added in v0.0.28
SlogLine returns a slog.Attr with key "line" and the integer line number of the call site as value. See q.SlogFile.
func Snake ¶ added in v0.0.42
Snake converts s to snake_case. CamelCase / PascalCase / kebab-case / space-separated inputs all produce lower_underscore output.
q.Snake("HelloWorld") // "hello_world"
q.Snake("XMLHttpRequest") // "xml_http_request"
q.Snake("hello-world") // "hello_world"
func Sort ¶ added in v0.0.53
Sort returns a sorted copy of slice in ascending order. Does NOT mutate the input — use stdlib `slices.Sort` for in-place sort. Sorting in a functional pipeline reads better when the input stays untouched.
func SortBy ¶ added in v0.0.53
SortBy returns a sorted copy ordered by `fn(elem)`. Stable — equal keys preserve input order.
byAge := q.SortBy(users, func(u User) int { return u.Age })
func SortFunc ¶ added in v0.0.53
SortFunc returns a sorted copy using less-style comparison — `less(a, b)` returns negative when a < b, 0 when equal, positive when a > b. Stable. Most general of the Sort* family; reach for q.Sort when ascending order is enough or q.SortBy when a projection produces the key.
func Sum ¶ added in v0.0.53
Sum adds all elements of a numeric slice. Empty input returns the zero value. Convenience over `q.Reduce(xs, func(a, b T) T { return a + b })`.
func TODO ¶ added in v0.0.15
func TODO(msg ...string)
TODO panics with "q.TODO <file>:<line>[: <msg>]" to mark an unfinished branch. Always an expression statement. Reach for q.Unreachable for code paths the author believes cannot execute.
func Tag ¶ added in v0.0.44
Tag returns the value of the struct-tag entry for `key` on T's `field`. T must be a struct (or pointer to struct). Both `field` and `key` MUST be Go string literals — the rewriter validates at compile time that the field exists. Returns "" when the tag is present but the key is absent (matches `reflect.StructTag.Get`).
type User struct {
ID int `json:"id" db:"user_id"`
Name string `json:"name,omitempty"`
}
q.Tag[User]("ID", "json") // "id"
q.Tag[User]("ID", "db") // "user_id"
q.Tag[User]("Name", "json") // "name,omitempty"
q.Tag[User]("Name", "db") // "" — key absent
func Take ¶ added in v0.0.47
Take returns the first n elements (or all of them if n exceeds len(slice)). Negative n is treated as 0.
func Tern ¶ added in v0.0.78
Tern returns ifTrue when cond evaluates to true, otherwise ifFalse. Despite the eager-looking call form, only the matching branch is evaluated — the preprocessor rewrites every call site at compile time. The runtime body is unreachable in a successful build.
func Timeout ¶ added in v0.0.20
Timeout derives a child context cancelled after dur. The preprocessor rewrites `ctx = q.Timeout(ctx, 5*time.Second)` (or `newCtx := q.Timeout(ctx, 5*time.Second)`) into the two-line idiom `ctx, _qCancelN := context.WithTimeout(ctx, dur); defer _qCancelN()` — the cancel function is hidden and auto-deferred in the enclosing function. Only valid in define (`:=`) or assign (`=`) position with a single LHS.
Example:
ctx = q.Timeout(ctx, 5*time.Second) reply := q.Try(call(ctx))
For "cancel early from another goroutine" flows, write `ctx, cancel := context.WithCancel(parent)` by hand — q.Timeout hides the cancel function, which is the wrong default when outside code needs to invoke it.
func Title ¶ added in v0.0.42
Title returns s with the first letter of each space-separated word upper-cased.
q.Title("hello world") // "Hello World"
func ToErr ¶ added in v0.0.14
ToErr adapts a `(T, *E)` call — where `*E` satisfies `error` — into `(T, error)` with a nil-check that collapses a typed-nil `*E` into a literal nil error. Use this to unblock a call rejected by q's typed-nil-interface guard:
// Foo declared as `func Foo() (int, *MyErr)`. v := q.Try(q.ToErr(Foo()))
Unlike every other helper in this package, ToErr is a plain runtime function — it is NOT rewritten by the preprocessor. Its body executes at runtime. The `interface{ *E; error }` constraint forces `*E` to implement error at compile time, so Go's type inference can figure out T/E/P from the call, and misuse (passing a non-error pointer) is caught statically.
ToErr is intentionally small and standalone — it's also useful outside q, for any API that returns a concrete error pointer and gets assigned into an `error` slot elsewhere.
func Trace ¶ added in v0.0.15
Trace forwards v when err is nil; otherwise the preprocessor rewrites the call site to bubble an error prefixed with the call site's file:line captured at compile time. Plain Go can't express this — runtime code has no access to its own source location without a stack walk. Reach for q.TraceE when the prefix needs to compose with the standard error-shape vocabulary.
Example:
func loadUser(id int) (User, error) {
row := q.Trace(db.Query(id)) // bubble prefixed with "users.go:42: "
...
}
func Try ¶
Try forwards v when err is nil; the preprocessor rewrites the call site into the inlined `if err != nil { return zero, err }` shape. Use q.TryE for chain-style custom error handling.
Example:
func loadUser(id int) (User, error) {
row := q.Try(db.Query(id))
user := q.Try(parse(row))
return user, nil
}
func TypeName ¶ added in v0.0.44
TypeName returns T's defined type name as a string. For named types: just the identifier (e.g. `"User"`, `"Color"`). For pointer types: the dereferenced name (`*User` → `"User"`). For slice / map / chan / function / unnamed-struct types: a representation matching `go/types` formatting.
The result is a constant the Go compiler folds into the binary — no runtime reflection is needed.
q.TypeName[User]() // "User" q.TypeName[*User]() // "User" q.TypeName[[]User]() // "[]User"
func Unreachable ¶ added in v0.0.15
func Unreachable(msg ...string)
Unreachable panics with "q.Unreachable <file>:<line>[: <msg>]" to mark code paths the author believes cannot execute. Always an expression statement.
func Unwrap ¶ added in v0.0.72
Unwrap takes a (T, error) pair and panics with the error when non- nil; otherwise returns v. Plain runtime function — NOT rewritten by the preprocessor.
q.Try is the right tool inside functions returning error: it rewrites to `if err != nil { return zero, err }` and bubbles cleanly. q.Try cannot bubble from main(), init(), test helpers, or any other function without an error return slot — q.Unwrap fills that gap with a panic-on-error escape hatch.
Use q.Unwrap when:
- The call site has no error return path (main, init, fixtures).
- You're asserting that a particular call cannot fail (config loaded once at startup, regexp compiled from a literal, q.Assemble of a graph proven correct at build time).
Avoid q.Unwrap in production library code where the caller might reasonably want to handle the error — that's q.Try territory.
Example:
func main() {
server := q.Unwrap(q.Assemble[*Server](newConfig, newDB, newServer))
server.Run()
}
func Unzip ¶ added in v0.0.51
Unzip splits a slice of pairs back into two parallel slices. The inverse of q.Zip:
names, ages := q.Unzip(q.Zip(names, ages)) // round-trip
func Upper ¶ added in v0.0.42
Upper returns format with all letters mapped to upper case (ASCII).
q.Upper("hello") // "HELLO"
func Values ¶ added in v0.0.50
func Values[K comparable, V any](m map[K]V) []V
Values returns a slice of m's values in unspecified order. Thin wrapper over `slices.Collect(maps.Values(m))`.
func WithAssemblyDebug ¶ added in v0.0.72
WithAssemblyDebug returns ctx with assembly trace output enabled. When this ctx is then passed to q.Assemble as an inline-value recipe (whether or not any other recipe consumes it), the rewriter emits per-step trace output to q.DebugWriter (defaults to os.Stderr) — recipe label per call. Useful for diagnosing "why did X get the wrong dep" without re-running with a debugger.
ctx := q.WithAssemblyDebug(context.Background()) server := q.Unwrap(q.Assemble[*Server](ctx, newConfig, newDB, newServer))
Use WithAssemblyDebugWriter to redirect output to a custom writer (test buffer, log file, etc.).
func WithAssemblyDebugWriter ¶ added in v0.0.72
WithAssemblyDebugWriter is WithAssemblyDebug with a caller-supplied destination writer. Mostly useful for tests where stdout/stderr shouldn't be mutated and a bytes.Buffer is the assertion target.
func WithPar ¶ added in v0.0.47
WithPar derives a child context that carries a worker-count limit for downstream q.Par* calls. limit must be > 0; non-positive values fall back to the default (runtime.NumCPU()) — use q.WithParUnbounded to opt out of the limit explicitly.
ctx = q.WithPar(ctx, 8) results := q.Try(q.ParMapErr(ctx, urls, fetch))
func WithParUnbounded ¶ added in v0.0.47
WithParUnbounded derives a child context that opts out of the worker-count limit — every element of a q.Par* op gets its own goroutine. Use sparingly; the bounded form scales better for slices large enough to matter.
func Yield ¶ added in v0.0.63
func Yield[T any](v T)
Yield emits v as the next value in an enclosing q.Generator's sequence. The preprocessor rewrites q.Yield(v) inside a Generator body into `if !yield(v) { return }` — early-exit when the consumer stops ranging.
Outside a Generator body, q.Yield panics at runtime via the universal stub — there is no yield function to bind to.
func ZipMap ¶ added in v0.0.51
func ZipMap[K comparable, V any](keys []K, values []V) map[K]V
ZipMap pairs keys and values into a map. Output length is min(len(keys), len(values)) — extras dropped. Collisions among keys are last-write-wins.
byID := q.ZipMap(ids, names)
Types ¶
type AssemblyResult ¶ added in v0.0.79
type AssemblyResult[T any] struct { // contains filtered or unexported fields }
AssemblyResult is the chain handle returned by Assemble / AssembleAll / AssembleStruct. Pick a chain terminator to choose the resource-lifetime policy:
.DeferCleanup() — returns (T, error). Cleanups fire automatically via a `defer` injected into the enclosing function, in REVERSE topo order. The fast path for "build it, use it, the function takes care of teardown when it returns".
.NoDeferCleanup() — returns (T, func(), error). Caller takes manual ownership of the shutdown closure (idempotent via sync.OnceFunc). Use when the assembled value's lifetime spans more than the enclosing function — e.g. main() that hands `shutdown` to a signal handler.
Cleanups can come from two sources: explicit recipes returning (T, func(), error), or auto-detected from T's type (Close() / Close() error / channel types). Pure (T, error) and bare-T recipes whose T isn't auto-cleanup-able simply add nothing to the chain — the same call composes cleanly with both.
func Assemble ¶ added in v0.0.70
func Assemble[T any](recipes ...any) AssemblyResult[T]
Assemble builds T from the supplied recipes. Returns an AssemblyResult[T]; pick `.DeferCleanup()` or `.NoDeferCleanup()` to terminate the chain and choose the resource-lifetime policy.
The preprocessor resolves the dep graph at compile time, topo- sorts the recipes, and emits the inlined construction. Recipes that produce closeable resources (whether by explicit `(T, func(), error)` shape or by T having a Close() method) have their cleanups collected and fired in reverse topo order during shutdown.
// Auto-defer pattern (most common): server, err := q.Assemble[*Server](newConfig, openDB, newServer).DeferCleanup() // Manual control (graceful shutdown spans main's lifetime): server, shutdown, err := q.Assemble[*Server](recipes...).NoDeferCleanup() defer shutdown() // Compose with q.Try: server := q.Try(q.Assemble[*Server](recipes...).DeferCleanup())
func AssembleAll ¶ added in v0.0.73
func AssembleAll[T any](recipes ...any) AssemblyResult[[]T]
AssembleAll is the multi-provider sibling of Assemble. When several recipes legitimately produce values assignable to T (plugins, handlers, middlewares — any aggregation pattern), q.Assemble would reject with "duplicate provider". AssembleAll opts into the multi- provider shape: every assignable recipe contributes one slice element in declaration order.
Returns AssemblyResult[[]T]; pick .DeferCleanup() or .NoDeferCleanup() to terminate. Same lifetime semantics as q.Assemble.
plugins, err := q.AssembleAll[Plugin](
newAuthPlugin, newLoggingPlugin, newMetricsPlugin,
).DeferCleanup()
func AssembleStruct ¶ added in v0.0.74
func AssembleStruct[T any](recipes ...any) AssemblyResult[T]
AssembleStruct is the field-decomposition sibling of Assemble. T must be a struct; each field is treated as a separate dep target. The preprocessor finds a recipe per field type, builds the shared dep graph once, and packs the results into the struct.
Returns AssemblyResult[T]; pick .DeferCleanup() or .NoDeferCleanup() to terminate. Same lifetime semantics as q.Assemble.
type App struct {
Server *Server
Worker *Worker
}
app, err := q.AssembleStruct[App](newConfig, openDB, newServer, newWorker).DeferCleanup()
q.AssembleStruct does NOT honor a recipe whose output IS T — choosing this entry IS the signal that you want field decomposition.
func (AssemblyResult[T]) DeferCleanup ¶ added in v0.0.84
func (r AssemblyResult[T]) DeferCleanup() (T, error)
DeferCleanup fires the assembled resource's cleanups via a `defer` injected into the enclosing function (reverse topo order). Returns (T, error). The runtime body is unreachable in a successful build.
func boot() (*Server, error) {
server, err := q.Assemble[*Server](newConfig, openDB, newServer).DeferCleanup()
if err != nil { return nil, err }
return server, nil
}
// db.Close() runs when boot returns, regardless of err path.
Compose with q.Try / q.Unwrap to drop the err:
server := q.Try(q.Assemble[*Server](recipes...).DeferCleanup())
func (AssemblyResult[T]) NoDeferCleanup ¶ added in v0.0.84
func (r AssemblyResult[T]) NoDeferCleanup() (T, func(), error)
NoDeferCleanup returns (T, shutdown, error) without any defer- injection. The caller controls when shutdown fires. The closure is idempotent (wraps sync.OnceFunc); duplicate calls are safe.
server, shutdown, err := q.Assemble[*Server](recipes...).NoDeferCleanup()
if err != nil { log.Fatal(err) }
defer shutdown()
context.AfterFunc(ctx, shutdown) // ctx cancel also triggers
func (AssemblyResult[T]) WithScope ¶ added in v0.0.101
func (r AssemblyResult[T]) WithScope(s *Scope) (T, error)
WithScope binds the assembly to a q.Scope. Each step consults the scope's cache before invoking its recipe; built deps and their cleanups register with the scope on the assembly's success path. Mutually exclusive with .DeferCleanup() / .NoDeferCleanup() — the scope is the sole lifetime owner.
scope := q.NewScope().DeferCleanup() server := q.Try(q.Assemble[*Server](recipes...).WithScope(scope))
Cache hits skip both the recipe call AND the cleanup registration. Cache misses build fresh and stage the cleanup for atomic registration with the scope on success.
If the scope is closed before or during the assembly, returns (zero, q.ErrScopeClosed). Fresh deps built in this call before the close fire their staged cleanups locally; pre-cached deps are not affected (their cleanups were registered earlier and fire when the scope itself closes).
Concurrent assemblies in the same scope may both build the same fresh type before either commits — last-writer-wins on the cache, both cleanups end up registered. Document the orphaning risk; use a singleflight wrapper in the recipe if exact-once build is required.
type Atom ¶ added in v0.0.94
type Atom string
Atom is the parent typed-string type from which user-declared atom types derive: `type MyAtom q.Atom`. The underlying string value of each atom is its declaring package's import path + the type's bare name, set by q.A[T]() via the preprocessor rewrite.
func AtomOf ¶ added in v0.0.94
AtomOf is q.A's case-friendly sibling: it returns the value cast as the parent q.Atom type, so it slots into a `switch a q.Atom { case … }` directly without the boilerplate q.Atom(...) wrap. The preprocessor rewrites each call site to q.Atom("<importPath>.<TypeName>").
Compare:
case q.Atom(q.A[Done]()): // verbose — wrap to bridge typed atom -> q.Atom case q.AtomOf[Done](): // identical, less noise
Use q.A when you want T-typed values that the type system protects (function params typed `func(p Pending)`, struct fields, returns). Use q.AtomOf when you're dealing with the atom in its erased q.Atom form — most commonly in switch cases on a q.Atom-typed value.
type CheckResult ¶ added in v0.0.6
type CheckResult struct {
// contains filtered or unexported fields
}
CheckResult carries a captured error for the q.CheckE chain. Like ErrResult.err, CheckResult.err is documented but never read at run time — the rewriter splices the captured error into the inlined bubble.
func CheckCtxE ¶ added in v0.0.126
func CheckCtxE(ctx context.Context) CheckResult
CheckCtxE is the chain variant of q.CheckCtx. Reuses CheckResult so the chain vocabulary is identical to q.CheckE — Err / ErrF / Wrap / Wrapf / Catch — applied to ctx.Err() before the bubble fires.
func CheckE ¶ added in v0.0.6
func CheckE(err error) CheckResult
CheckE starts an error-only chain; see ErrResult for the vocabulary. The methods here return void — there is no value to thread through, only an error to shape.
func (CheckResult) Catch ¶ added in v0.0.6
func (r CheckResult) Catch(fn func(error) error)
Catch transforms the captured error via fn. Returning nil suppresses the bubble (continue past the CheckE call); returning non-nil bubbles that error in place of the original. There is no value to recover, unlike ErrResult.Catch.
func (CheckResult) Err ¶ added in v0.0.6
func (r CheckResult) Err(replacement error)
Err bubbles the supplied constant error when the captured err is non-nil.
func (CheckResult) ErrF ¶ added in v0.0.6
func (r CheckResult) ErrF(fn func(error) error)
ErrF bubbles fn(capturedErr).
func (CheckResult) Wrap ¶ added in v0.0.6
func (r CheckResult) Wrap(msg string)
Wrap bubbles fmt.Errorf("<msg>: %w", err).
func (CheckResult) Wrapf ¶ added in v0.0.6
func (r CheckResult) Wrapf(format string, args ...any)
Wrapf bubbles fmt.Errorf(format + ": %w", args..., err).
type Codec ¶ added in v0.0.56
Codec encodes and decodes values of type T to/from bytes. Used by q.AtCompileTime to transport values from the preprocessor-time subprocess to the runtime user package.
func BinaryCodec ¶ added in v0.0.56
BinaryCodec returns the encoding/binary codec for T. Fixed-size types only (no slices, maps, strings) — produces the smallest possible output.
type ConvertOption ¶ added in v0.0.107
type ConvertOption struct {
// contains filtered or unexported fields
}
ConvertOption is the sealed marker type for q.ConvertTo overrides. Constructed only via q.Set / q.SetFn; the rewriter parses each option's call AST at compile time and erases the runtime call.
func Set ¶ added in v0.0.107
func Set[V any](targetField V, value V) ConvertOption
Set overrides the value of targetField with value.
targetField MUST be a Target{}.<FieldName> selector expression so the rewriter can extract the field path and so Go's own type-checker validates the field reference. The generic param V is unified with both the field's type and the override value's type — assignability is enforced by Go itself.
q.ConvertTo[UserDTO](u, q.Set(UserDTO{}.Source, "v1"))
// → UserDTO{..., Source: "v1"}
// Refactor-safe: rename UserDTO.Source → SourceTag and Go's
// compiler flags this call site immediately.
func SetFn ¶ added in v0.0.107
func SetFn[Source, V any](targetField V, fn func(Source) V) ConvertOption
SetFn overrides the value of targetField with the result of fn(src).
targetField MUST be a Target{}.<FieldName> selector expression (same shape as q.Set). The generic param V unifies the field's type with the function's return type; Source unifies with the surrounding q.ConvertTo call's source type. The function is invoked with the source value at the rewritten call site — closures capture surrounding scope normally.
q.ConvertTo[UserDTO](u, q.SetFn(UserDTO{}.FullName, func(u User) string {
return u.First + " " + u.Last
}))
// → UserDTO{..., FullName: (func(u User) string { ... })(u)}
For fallible derivations (calling a database, a remote service, parsing input that might be invalid), use q.SetFnE inside q.ConvertToE instead.
func SetFnE ¶ added in v0.0.111
func SetFnE[Source, V any](targetField V, fn func(Source) (V, error)) ConvertOption
SetFnE is the fallible variant of q.SetFn: the function returns (V, error). A non-nil error short-circuits the whole conversion and propagates out of q.ConvertToE.
Only valid inside q.ConvertToE (the bare q.ConvertTo has no error slot). The rewriter rejects q.SetFnE inside q.ConvertTo with a build-time diagnostic.
q.SetFnE(UserDTO{}.Email, func(u User) (string, error) {
return db.LookupEmail(u.ID)
})
type Coroutine ¶ added in v0.0.52
type Coroutine[I, O any] struct { // contains filtered or unexported fields }
Coroutine is a bidirectional, suspendable computation. Construct with q.Coro; drive with .Resume / .Close.
func Coro ¶ added in v0.0.52
Coro spawns body in a goroutine and returns a Coroutine handle. body reads inputs from `in` and writes outputs to `out`; when it returns, the coroutine is finished and subsequent .Resume calls return zero, false.
Either side may close the conversation: the caller via .Close(), or the body by returning. Closing is idempotent.
doubler := q.Coro(func(in <-chan int, out chan<- int) {
for v := range in {
out <- v * 2
}
})
defer doubler.Close()
v, _ := doubler.Resume(21) // 42
v, _ = doubler.Resume(100) // 200
Send/receive ordering matters: the body must read from `in` before it sends on `out` for each step (or the .Resume call will block forever). q.Coro doesn't enforce this — it's a cooperative protocol between caller and body.
func (*Coroutine[I, O]) Close ¶ added in v0.0.52
func (c *Coroutine[I, O]) Close()
Close signals the body that the conversation is over by closing the input channel. The body's `for range in` loop terminates cleanly; bodies that don't range over `in` should select on it closing to detect Close.
Idempotent. Safe to call from any goroutine. Returns immediately; the body's goroutine may still run for a bit while finishing up. Use .Wait if you need to block until the body is fully done.
func (*Coroutine[I, O]) Done ¶ added in v0.0.52
Done reports whether the body has finished (either by returning on its own or because .Close was called and the body has now drained). Non-blocking.
func (*Coroutine[I, O]) Resume ¶ added in v0.0.52
Resume sends v to the coroutine's body and waits for the next value the body produces. Returns (value, true) on success, or (zero, false) if the body has finished (returned) or .Close was called.
Resume is safe to call from any goroutine but only one Resume call can be in-flight at a time; concurrent Resumes deadlock with each other (each would block waiting for the body to read its input).
type ErrResult ¶
type ErrResult[T any] struct { // contains filtered or unexported fields }
ErrResult carries a captured (value, err) pair for the q.TryE chain. The receiver fields are extracted from the source call by the preprocessor when emitting the rewritten if-err-then-return shape; the struct itself is never materialized in production code.
func AwaitAllCtxE ¶ added in v0.0.20
AwaitAllCtxE is the chain variant of AwaitAllCtx.
func AwaitAllE ¶ added in v0.0.20
AwaitAllE is the chain variant of AwaitAll. Reuses ErrResult with element type []T — the bubbled error is whichever error AwaitAllRaw saw first.
func AwaitAnyCtxE ¶ added in v0.0.20
AwaitAnyCtxE is the chain variant of AwaitAnyCtx.
func AwaitCtxE ¶ added in v0.0.20
AwaitCtxE is the chain variant of AwaitCtx; reuses ErrResult[T] so the chain vocabulary is identical to TryE / AwaitE.
func AwaitE ¶ added in v0.0.15
AwaitE is the chain variant of Await; reuses ErrResult[T] so the chain vocabulary is identical to TryE.
func DrainAllCtxE ¶ added in v0.0.20
DrainAllCtxE is the chain variant of DrainAllCtx.
func RecvAnyCtxE ¶ added in v0.0.20
RecvAnyCtxE is the chain variant of RecvAnyCtx.
func RecvCtxE ¶ added in v0.0.20
RecvCtxE is the chain variant of RecvCtx. Reuses ErrResult[T] so the chain vocabulary is identical to TryE — the bubbled error is either ctx.Err() or q.ErrChanClosed, shaped by the chain method.
func TryE ¶
TryE wraps a (T, error) pair into an ErrResult so the caller can chain a custom error handler. The full chain — q.TryE(call).Method(…) — is rewritten as one expression by the preprocessor.
func (ErrResult[T]) Catch ¶
Catch handles a non-nil err via fn, which returns either a recovered value (T, nil) — used in place of the bubble — or a new error (zero, err) — bubbled in place of the original. The most powerful chain method; reach for Err / ErrF / Wrap / Wrapf for the simpler shapes.
func (ErrResult[T]) Err ¶
Err bubbles the supplied constant error when the captured err is non-nil. The original err is discarded.
func (ErrResult[T]) ErrF ¶
ErrF bubbles fn(capturedErr). Use this for type-mapping or annotation that needs the original err (e.g. errors.Is / errors.As inspection).
func (ErrResult[T]) RecoverAs ¶ added in v0.0.25
RecoverAs is the errors.As-flavoured chain-continuing recovery. When the captured err can be extracted into the type carried by typedNil (a typed-nil literal such as `(*MyErr)(nil)`), the chain's value becomes value and the err is cleared. The preprocessor extracts the target type syntactically from the typedNil arg at compile time, so it must be a typed-nil expression (e.g. `(*strconv.NumError)(nil)`); arbitrary error values are rejected with a diagnostic.
Like RecoverIs, RecoverAs must be followed by a terminal method.
Example:
n := q.TryE(strconv.Atoi(s)).
RecoverAs((*strconv.NumError)(nil), -1).
Wrapf("parsing %q", s)
func (ErrResult[T]) RecoverIs ¶ added in v0.0.25
RecoverIs is a chain-continuing recovery: when the captured err matches sentinel via errors.Is, the chain's value becomes value and the err is cleared. Otherwise the err passes through to the next chain step. The intermediate result is still ErrResult[T], so RecoverIs MUST be followed by a terminal method (Err, ErrF, Catch, Wrap, Wrapf) — using it as the chain's last step is a build-time error from the preprocessor (the bubble would be silently swallowed otherwise).
Example:
n := q.TryE(strconv.Atoi(s)).
RecoverIs(strconv.ErrSyntax, 0).
Wrapf("parsing %q", s)
// Returns 0 if s isn't a valid syntax; bubbles the wrapped err otherwise.
Multiple RecoverIs / RecoverAs steps may be chained in source order; each runs its check only if no earlier step has already recovered.
func (ErrResult[T]) Wrap ¶
Wrap is the no-format sugar for fmt.Errorf("<msg>: %w", err) on the bubble path. Reach for Wrapf when the message needs format args.
func (ErrResult[T]) Wrapf ¶
Wrapf is the fmt.Errorf-with-%w sugar. The captured err is appended as the final %w arg by the preprocessor; the supplied format need not include it.
Example:
user := q.TryE(loadUser(id)).Wrapf("loading user %d", id)
// rewrites to: if err != nil { return zero, fmt.Errorf("loading user %d: %w", id, err) }
type FnParams ¶ added in v0.0.90
type FnParams struct{}
FnParams is the opt-in marker for required-by-default *function parameter* structs — Go's stand-in for named arguments to a function. Add `_ q.FnParams` as a blank field, and the preprocessor validates every struct literal of that type at compile time: every named field must be keyed in the literal unless it carries a `q:"optional"` (or `q:"opt"`) tag.
Build-failure example:
type LoadOptions struct {
_ q.FnParams
Path string // required
Format string // required
Timeout time.Duration `q:"opt"` // optional
}
func main() {
Load(LoadOptions{Path: "/etc", Format: "yaml"}) // OK
Load(LoadOptions{Path: "/etc"}) // ✗ build error:
// // q.FnParams: required field(s) [Format] not set in
// // LoadOptions literal (mark optional fields with
// // `q:"optional"` to opt them out)
Load(LoadOptions{}) // ✗ build error: required field(s) [Format Path] not set
}
Properties.
- Zero size; the field adds no bytes to the struct layout.
- Marker presence is detected at compile time via go/types; no runtime cost.
- Limit: only struct literals at their construction site are checked. `p := LoadOptions{}; Load(p)` is validated at the literal — not at the Load call.
Plain runtime type — NOT rewritten by the preprocessor. The type itself is just an empty struct; all the work happens in the preprocessor's validation pass.
type Future ¶ added in v0.0.15
type Future[T any] struct { // contains filtered or unexported fields }
Future is the promise-like handle returned by q.Async. Internal state: a buffered channel the spawned goroutine sends the result on, consumed at-most-once by q.Await / q.AwaitRaw.
type GenMarker ¶ added in v0.0.43
type GenMarker struct{}
GenMarker is the value type the Gen* directives return. It carries no runtime data — its only purpose is to keep `var _ = q.GenX[T]()` type-checkable.
func GenEnumJSONLax ¶ added in v0.0.43
func GenEnumJSONLax[T comparable]() GenMarker
GenEnumJSONLax synthesizes JSON marshallers that **preserve unknown values** for forward-compat. Wire format is the underlying type:
- For string-backed T: marshal/unmarshal as the underlying string. Any string is accepted on unmarshal. Marshal emits the underlying string verbatim.
- For int-backed T: marshal/unmarshal as the underlying int. Any int is accepted; the wire value is preserved.
Use when forward-compat matters more than canonical-shape: services receiving values your code doesn't yet understand will preserve them through round-trip. Combine with q.Exhaustive's `default:` clause to handle the genuinely-unknown values explicitly.
type Status string
const (Pending Status = "pending"; Done Status = "done")
var _ = q.GenEnumJSONLax[Status]()
// → Status("future_value") survives unmarshal+marshal unchanged
func GenEnumJSONStrict ¶ added in v0.0.43
func GenEnumJSONStrict[T comparable]() GenMarker
GenEnumJSONStrict synthesizes name-based JSON marshallers that **error on unknown values**. Use when wire drift should crash loudly — newer producers introducing values your service doesn't know about will fail to deserialize. Compatible with q.Exhaustive (the type is closed: every value parsed in must be a declared constant).
type Color int const (Red Color = iota; Green; Blue) var _ = q.GenEnumJSONStrict[Color]() // → MarshalJSON encodes "Red" / "Green" / "Blue" // → UnmarshalJSON errors on `"Magenta"`
Unknown names on unmarshal: returns an error wrapping q.ErrEnumUnknown. Marshal: every reachable value at runtime should already be a declared constant (since UnmarshalJSON would have rejected anything else); if a manually-cast Color(99) reaches MarshalJSON, the helper returns the unknown error.
func GenStringer ¶ added in v0.0.43
func GenStringer[T comparable]() GenMarker
GenStringer synthesizes a `func (T) String() string` method on T using q.EnumName as the underlying lookup. T must have constants declared in its home package (same restriction as q.EnumValues).
type Color int const (Red Color = iota; Green; Blue) var _ = q.GenStringer[Color]() // → companion file declares `func (c Color) String() string`
String() returns the constant identifier name on a known value, or the empty string on unknown values. Pair with q.EnumValid at canonical sites if you need to detect unknowns.
func Sealed ¶ added in v0.0.99
Sealed declares the closed-set of variant types for the marker interface I. The preprocessor extracts I's single marker method from go/types, then synthesises a `func (V) markerName() {}` on each variadic variant V in a companion file, so each V satisfies I via the synthesised marker.
I must be an interface with exactly one method (no args, no results). Each variant must be a defined named type in the same package as the q.Sealed call. Returns a GenMarker so the call can sit in `var _ = ...` at package level (same shape as q.GenStringer).
The Sealed declaration also registers the closed set with the preprocessor, enabling q.Exhaustive coverage on type switches over I values and q.Match + q.OnType integration.
Example:
type Message interface{ message() }
type Ping struct{ ID int }
type Pong struct{ ID int }
type Disconnect struct{ Reason string }
var _ = q.Sealed[Message](Ping{}, Pong{}, Disconnect{})
After preprocessing, each variant satisfies Message:
var m Message = Ping{ID: 1} // OK — synthesised Ping.message()
type LazyValue ¶ added in v0.0.88
type LazyValue[T any] struct { // contains filtered or unexported fields }
Lazy is the deferred-value handle. The fields are unexported and only set by q.LazyFromThunk; the rewriter never materialises this struct directly at user call sites.
func Lazy ¶ added in v0.0.88
Lazy wraps the value expression in a thunk via the preprocessor. The runtime body is unreachable in a successful build — every q.Lazy(<expr>) call site is rewritten away to q.LazyFromThunk(...).
func LazyFromThunk ¶ added in v0.0.88
LazyFromThunk constructs a *LazyValue[T] from an explicit thunk. Callers should normally write q.Lazy(<expr>) and let the preprocessor wrap the expression in a thunk; LazyFromThunk is the underlying real constructor that the rewriter targets, exported so the rewritten code can reach it.
Plain runtime function — NOT rewritten by the preprocessor. Safe to use directly when you genuinely have a hand-written thunk and want the same semantics without going through the q.Lazy(<expr>) sugar.
type LazyValueE ¶ added in v0.0.88
type LazyValueE[T any] struct { // contains filtered or unexported fields }
LazyValueE is the (T, error)-shaped sibling of LazyValue. The wrapped thunk runs once on first .Value() under sync.Once; both T and error are cached and returned on every subsequent call.
func LazyE ¶ added in v0.0.88
func LazyE[T any](v T, err error) *LazyValueE[T]
LazyE wraps a (T, error)-returning call site in a thunk via the preprocessor. The user writes:
l := q.LazyE(loadConfig()) // loadConfig() (Config, error)
and the rewriter emits q.LazyEFromThunk(func() (Config, error) { return loadConfig() }). The runtime body is unreachable in a successful build.
At the consumer, pair .Value() with q.Try:
cfg := q.Try(l.Value())
func LazyEFromThunk ¶ added in v0.0.88
func LazyEFromThunk[T any](thunk func() (T, error)) *LazyValueE[T]
LazyEFromThunk constructs a *LazyValueE[T] from an explicit thunk. The rewriter targets this constructor when it lowers q.LazyE(<call>); callers may also reach for it directly when they have a hand-written (T, error) thunk.
Plain runtime function — NOT rewritten by the preprocessor.
func (*LazyValueE[T]) IsForced ¶ added in v0.0.88
func (l *LazyValueE[T]) IsForced() bool
IsForced reports whether the underlying thunk has been evaluated. Diagnostic only.
func (*LazyValueE[T]) Value ¶ added in v0.0.88
func (l *LazyValueE[T]) Value() (T, error)
Value evaluates the wrapped thunk on the first call (under sync.Once) and returns the cached (T, error) pair on every subsequent call. A non-nil error from the first call is cached too — repeated calls keep returning it; the thunk does not retry.
type MatchArm ¶ added in v0.0.67
type MatchArm[R any] struct { // contains filtered or unexported fields }
MatchArm is one arm of a q.Match. Constructed via q.Case / q.Default; the preprocessor consumes them at compile time, so the runtime body is not reached in a successful build.
R is a phantom type parameter — the compile-time-consumed nature of the arm means the result isn't actually needed at runtime, but the type parameter must still flow through for q.Match to type- check. The `[0]R` zero-size array consumes the type parameter without taking any space.
func Case ¶ added in v0.0.45
Case is the universal arm. cond decides whether this arm fires; result is what the arm returns when it does.
The preprocessor inspects cond's type via go/types and dispatches:
cond is matched-value-typed → value match (v == cond) cond is bool → predicate match (if cond) cond is func() V → lazy value match (v == cond()) cond is func() bool → lazy predicate (if cond())
Both cond and result are source-rewritten regardless of type — the preprocessor extracts their literal source text and re-emits it inside the rewritten if/case body, so expressions only run on the matching arm. `q.Case(0, expensive())` only calls expensive() when v==0.
Examples:
q.Case(Red, "warm") // value match (cond is the enum type) q.Case(n > 0, "positive") // predicate (cond is bool) q.Case(getThreshold(), "x") // value match, lazy via source rewrite q.Case(predFn, "x") // lazy predicate; predFn is func()bool q.Case(0, expensive()) // lazy result via source rewrite
To pass a result that is itself a function value (rather than the function's call result), write the call explicitly: `q.Case(0, makeFallback())` not `q.Case(0, makeFallback)`.
func Default ¶ added in v0.0.15
Default is the catch-all arm. Up to one q.Default per q.Match call. When present, the typecheck pass skips the missing-constants coverage check (the catch-all covers anything missing).
q.Match(c, q.Case(Red, "warm"), q.Default("unknown"))
func OnType ¶ added in v0.0.96
q.Exhaustive on a OneOfN-derived value
Plain q.Match isn't the only dispatch site — q.Exhaustive enforces coverage on a statement-level type switch over the unwrapped value:
switch v := q.Exhaustive(s.Value).(type) {
case Pending: // payload-less variant
case Done: fmt.Println(v.At)
case Failed: fmt.Println(v.Err)
}
The build fails if any variant is missing. `default:` opts out of the missing-case rule but doesn't substitute for covering declared variants — same semantics as the const-enum form.
q.Exhaustive(s.Value) is valid Go to gopls (q.Exhaustive is the identity on type T); the .(type) assertion works because s.Value's static type is `any`. The typecheck pass spots the OneOfN ancestor of `s.Value` and walks the variant list to drive the coverage check.
OnType is a q.Match arm that fires when the matched OneOfN-derived value's runtime variant is T. handler receives the unwrapped typed variant value. R is inferred from handler's return type.
Only valid as an argument to q.Match — used anywhere else the runtime stub panics. The matched value's type MUST be a OneOfN- derived sum (a defined type whose underlying type is q.OneOfN[…]), or `q.OneOfN[…]` directly.
Coverage check: when q.Match has q.OnType arms, every variant of the matched sum type must have an OnType arm OR a q.Default arm must cover the rest. Build fails otherwise. Variants may also be covered indirectly by q.Default; mixing is allowed.
Example:
type Status q.OneOf3[Pending, Done, Failed]
desc := q.Match(s,
q.OnType(func(p Pending) string { return "waiting" }),
q.OnType(func(d Done) string { return "done" }),
q.OnType(func(f Failed) string { return "failed" }),
)
Mixing q.OnType with q.Case in the same q.Match is rejected — the dispatch shape is incompatible (OnType dispatches by Tag; Case by value-equality / predicate). Use one or the other.
type NilResult ¶
type NilResult[T any] struct { // contains filtered or unexported fields }
NilResult carries a captured *T for the q.NotNilE chain. Methods mirror ErrResult's vocabulary; the absence of an incoming err means ErrF / Catch take thunks (no error parameter).
func NotNilE ¶
NotNilE wraps a *T into a NilResult for chain-style nil handling. See NotNil for the bare bubble form.
func (NilResult[T]) Catch ¶
Catch handles a nil pointer via fn, which returns either a recovered pointer (*T, nil) — used in place of the bubble — or a new error (nil, err) — bubbled. Mirrors ErrResult.Catch.
func (NilResult[T]) ErrF ¶
ErrF computes the bubble error via fn — useful when the error needs runtime work to assemble (formatting against captured locals, joined errors, etc.).
func (NilResult[T]) Wrap ¶
Wrap bubbles errors.New(msg). There is no source error to %w-wrap on the nil branch; the message stands alone.
func (NilResult[T]) Wrapf ¶
Wrapf bubbles fmt.Errorf(format, args...). No %w is appended — there is no source error on the nil branch — so the supplied format is the full message.
Example:
user := q.NotNilE(table[id]).Wrapf("no user %d", id)
// rewrites to: if p == nil { return nil, fmt.Errorf("no user %d", id) }
type OkResult ¶ added in v0.0.15
type OkResult[T any] struct { // contains filtered or unexported fields }
OkResult carries a captured (value, ok) pair for the q.OkE chain. The receiver fields are extracted by the preprocessor when emitting the rewritten if-not-ok-then-return shape; the struct itself is never materialized in production code.
func AsE ¶ added in v0.0.15
AsE wraps a type assertion into an OkResult so the OkE chain vocabulary shapes the bubble when the assertion fails.
func OkE ¶ added in v0.0.15
OkE wraps a (T, bool) pair into an OkResult so the caller can chain a custom error handler. The full chain — q.OkE(call).Method(…) — is rewritten as one expression by the preprocessor. Mirrors NotNilE's vocabulary: there is no source error on the false-ok branch, so ErrF takes a thunk and Wrap/Wrapf build the error from scratch (errors.New / fmt.Errorf without %w).
func RecvE ¶ added in v0.0.15
RecvE wraps a channel receive into an OkResult (shared with Ok), so the full OkE chain vocabulary shapes the bubble when the channel is closed.
func (OkResult[T]) Catch ¶ added in v0.0.15
Catch handles a not-ok value via fn, which returns either a recovered (T, nil) — used in place of the bubble — or a new error (zero, err) — bubbled. Mirrors NotNilE.Catch's shape.
func (OkResult[T]) Err ¶ added in v0.0.15
Err bubbles the supplied constant error when the captured ok is false.
func (OkResult[T]) ErrF ¶ added in v0.0.15
ErrF computes the bubble error via fn — useful when the error needs runtime work to assemble. No captured source error (the not-ok branch has none), so fn takes no arguments.
func (OkResult[T]) Wrap ¶ added in v0.0.15
Wrap bubbles errors.New(msg). There is no source error to %w-wrap on the not-ok branch; the message stands alone.
func (OkResult[T]) Wrapf ¶ added in v0.0.15
Wrapf bubbles fmt.Errorf(format, args...). No %w is appended — there is no source error on the not-ok branch — so the supplied format is the full message.
Example:
user := q.OkE(users[id]).Wrapf("no user %d", id)
// rewrites to: if !ok { return zero, fmt.Errorf("no user %d", id) }
type OneOf2 ¶ added in v0.0.96
OneOf2 is the binary discriminated union over arm types A and B. Tag is 1 for A, 2 for B (0 for the zero / unconstructed value).
Don't construct directly. q.AsOneOf[T](v) is the only sanctioned surface — the preprocessor validates the variant and emits the composite-literal shape with the right Tag.
type OneOf3 ¶ added in v0.0.96
OneOf3 is the ternary discriminated union over arm types A, B, C. Tag is 1 for A, 2 for B, 3 for C.
type OpenResult ¶ added in v0.0.6
type OpenResult[T any] struct { // contains filtered or unexported fields }
OpenResult is the plain Open handle — it exposes only .DeferCleanup (and .NoDeferCleanup) so IDE completion stays focused on the common case.
func Open ¶ added in v0.0.6
func Open[T any](v T, err error) OpenResult[T]
Open begins a resource acquisition chain: pass in a (T, error)- returning call, then chain `.DeferCleanup(cleanup)` to bubble on error and register `defer cleanup(resource)` on success in the enclosing function. Reach for q.OpenE for chain-style custom error handling around the bubble.
Example:
conn := q.Open(dial(addr)).DeferCleanup((*Conn).Close)
// equivalent, post-rewrite, to:
// conn, err := dial(addr)
// if err != nil { return zero, err }
// defer conn.Close()
func (OpenResult[T]) DeferCleanup ¶ added in v0.0.84
func (r OpenResult[T]) DeferCleanup(cleanup ...any) T
DeferCleanup bubbles err on failure; registers `defer cleanup(v)` in the enclosing function and returns v on success.
Two forms:
- DeferCleanup(cleanup) — explicit cleanup. Two accepted shapes: func(T) → defer cleanup(v) func(T) error → defer wrapper that slog.Errors the close-time err The cleanup MUST take the resource — q.Open's DeferCleanup is scoped to the resource it wraps. For cleanups that don't need the resource, write `defer myCleanup()` at the call site. The preprocessor validates the argument at compile time; any other shape is a build error. Wrap the cleanup yourself for different handling on the close-time error — suppress, retry, or transform.
- DeferCleanup() — no args; the preprocessor infers the cleanup from T's type at compile time. Supported shapes: bidirectional and send-only channels (rewrites to `defer close(v)` — recv-only channels are rejected since the consumer doesn't own close), types with a `Close() error` method (rewrites to a deferred wrapper that logs the error via `slog.Error`), and types with a `Close()` method (rewrites to `defer v.Close()`). Any other T is a build error — pass an explicit cleanup or use .NoDeferCleanup() to opt out.
The cleanup parameter is `...any` so the source compiles whether the caller hands in `func(T)` or `func(T) error`; the preprocessor rejects anything else with a typed diagnostic. Calls with two-or- more args are also rejected by the preprocessor.
func (OpenResult[T]) NoDeferCleanup ¶ added in v0.0.84
func (r OpenResult[T]) NoDeferCleanup() T
NoDeferCleanup bubbles err on failure and returns v on success without registering any deferred cleanup. Use it to make the "no cleanup needed" intent explicit at the call site, instead of passing a do-nothing function to .DeferCleanup. The bubble path is identical to .DeferCleanup's; only the success path differs.
func (OpenResult[T]) WithScope ¶ added in v0.0.137
func (r OpenResult[T]) WithScope(args ...any) T
WithScope bubbles err on failure; on success attaches the resource to scope so the scope owns its lifetime — when the scope closes, the cleanup fires.
Two call shapes:
- WithScope(scope) — auto-detect cleanup from T (same shapes DeferCleanup() infers: bidirectional/send-only chan, Close(), Close() error).
- WithScope(cleanup, scope) — explicit cleanup (func(T) or func(T) error).
If the scope is already closed when the attach fires, the cleanup runs eagerly and q.ErrScopeClosed is bubbled — different from DeferCleanup which always succeeds. The `args` parameter is `...any` so the source compiles for either shape; the preprocessor enforces the (cleanup?, scope) ordering at build time.
type OpenResultE ¶ added in v0.0.6
type OpenResultE[T any] struct { // contains filtered or unexported fields }
OpenResultE is the chain-capable Open handle. Shape methods return OpenResultE[T] so DeferCleanup can terminate the chain; DeferCleanup itself returns T.
func OpenE ¶ added in v0.0.6
func OpenE[T any](v T, err error) OpenResultE[T]
OpenE is Open with chainable error-shape methods. Shape methods (Err/ErrF/Wrap/Wrapf/Catch) return OpenResultE[T] so `.DeferCleanup` can still follow as the terminal. DeferCleanup is the only member that returns T; everything else in the chain is a pass-through modifier on the bubbled error.
Example:
conn := q.OpenE(dial(addr)).Wrap("dialing").DeferCleanup((*Conn).Close)
func (OpenResultE[T]) Catch ¶ added in v0.0.6
func (r OpenResultE[T]) Catch(fn func(error) (T, error)) OpenResultE[T]
Catch recovers or transforms: fn(err) returns (T, nil) to recover with a value (replaces the bubble, the recovered T feeds DeferCleanup) or (zero, newErr) to bubble newErr instead of the original.
func (OpenResultE[T]) DeferCleanup ¶ added in v0.0.84
func (r OpenResultE[T]) DeferCleanup(cleanup ...any) T
DeferCleanup bubbles the shaped error on failure; registers `defer cleanup(v)` in the enclosing function and returns v on success. Same explicit-cleanup shapes (`func(T)` or `func(T) error`) and same auto-cleanup inference as q.Open.DeferCleanup — see that doc for the supported T shapes.
func (OpenResultE[T]) Err ¶ added in v0.0.6
func (r OpenResultE[T]) Err(replacement error) OpenResultE[T]
Err replaces the captured error with a constant.
func (OpenResultE[T]) ErrF ¶ added in v0.0.6
func (r OpenResultE[T]) ErrF(fn func(error) error) OpenResultE[T]
ErrF transforms the captured error via fn(err) error.
func (OpenResultE[T]) NoDeferCleanup ¶ added in v0.0.84
func (r OpenResultE[T]) NoDeferCleanup() T
NoDeferCleanup bubbles the shaped error on failure and returns v on success without registering any deferred cleanup. Same semantics as q.OpenResult.NoDeferCleanup but composes with the shape methods (Wrap/Wrapf/Err/ErrF/Catch) on q.OpenE.
func (OpenResultE[T]) WithScope ¶ added in v0.0.137
func (r OpenResultE[T]) WithScope(args ...any) T
WithScope bubbles the shaped error on failure; on success attaches the resource to scope. Same semantics and call shapes as q.OpenResult.WithScope, composed with the shape methods (Wrap / Wrapf / Err / ErrF / Catch).
func (OpenResultE[T]) Wrap ¶ added in v0.0.6
func (r OpenResultE[T]) Wrap(msg string) OpenResultE[T]
Wrap bubbles fmt.Errorf("<msg>: %w", err).
func (OpenResultE[T]) Wrapf ¶ added in v0.0.6
func (r OpenResultE[T]) Wrapf(format string, args ...any) OpenResultE[T]
Wrapf bubbles fmt.Errorf(format + ": %w", args..., err).
type Pair ¶ added in v0.0.51
type Pair[A, B any] struct { First A Second B }
Pair carries two values. Used by q.Zip / q.Unzip and any callers that want a tiny tuple-like value type. Field names follow Go's stdlib (e.g. `database/sql.NullString.{Valid, String}`-ish — not `Key/Value` because Pair isn't always a key/value pair).
type PanicError ¶ added in v0.0.15
PanicError wraps a recovered panic value and the stack captured at recovery time. Produced by q.Recover and by the default q.RecoverE path when no user-supplied mapper is provided. Callers recover the original panic data with:
var pe *q.PanicError
if errors.As(err, &pe) { fmt.Println(pe.Value, string(pe.Stack)) }
func (*PanicError) Error ¶ added in v0.0.15
func (e *PanicError) Error() string
Error renders the panic value and a short stack hint. The full stack is available on the Stack field.
type PathChain ¶ added in v0.0.86
type PathChain[T any] struct { // contains filtered or unexported fields }
PathChain carries the running q.At chain. Its methods are all rewritten away — the struct is never materialized in production code.
func At ¶ added in v0.0.86
At begins a nested-nil safe traversal chain. The argument is a path expression — a selector chain like `user.Profile.Theme` is the common case; a single identifier or a call result also works. The returned PathChain[T] is closed by .Or(fallback) or .OrZero(); .OrElse(alt) chains additional paths / values to try before the terminal kicks in.
The runtime body is unreachable in a successful build — every q.At chain is rewritten away by the preprocessor.
func (PathChain[T]) Or ¶ added in v0.0.86
func (c PathChain[T]) Or(fallback T) T
Or terminates the chain with a literal/expression fallback. Returns the first non-nil path's value, falling back to fallback when every path is nil. fallback is evaluated lazily.
func (PathChain[T]) OrE ¶ added in v0.0.89
OrE terminates the chain by delegating to a (T, error)-returning fallback fetcher. Returns the first non-nil path's value; if every path is nil, the fetcher's call result drives the result — its T becomes the chain's value, its error (if non-nil) bubbles through the enclosing function's error return slot.
Spread form: pass a single (T, error)-returning call so Go's f(g())-multi-spread rule applies.
Example:
cfg := q.At(cache.Config).OrE(loadFromDisk(path)) // Cache hit -> use cached. Cache miss -> call loadFromDisk; its // error (if any) bubbles, its value (if ok) becomes the result.
func (PathChain[T]) OrElse ¶ added in v0.0.86
OrElse appends another path / value to try when every prior path has yielded nil. alt may itself be a selector chain (the rewriter walks it with the same per-hop nil checks) or any other expression (returned as-is when reached). alt is evaluated lazily — only when every prior path was nil.
func (PathChain[T]) OrError ¶ added in v0.0.89
OrError terminates the chain with an error bubble. Returns the first non-nil path's value; if every path is nil, the rewriter emits an early return that bubbles err through the enclosing function's error return slot. The enclosing function MUST have a trailing `error` return — same constraint as q.Try.
Example:
cfg := q.At(opts.Config).OrError(ErrConfigMissing) // Returns opts.Config when non-nil; bubbles ErrConfigMissing // otherwise (function must return ..., error).
type RecoverResult ¶ added in v0.0.15
type RecoverResult struct {
// contains filtered or unexported fields
}
RecoverResult carries the errPtr through the q.RecoverE chain. The terminal method (Map, Err, ErrF, Wrap, Wrapf) is the actual deferred function.
func RecoverE ¶ added in v0.0.15
func RecoverE(errPtr ...*error) RecoverResult
RecoverE begins a RecoverE chain. The chain method decides how the recovered panic (if any) maps to the error stored via errPtr. Like Recover, the chain's terminal method IS the deferred function, so recover() catches the panic correctly.
Two call shapes mirror Recover:
- `defer q.RecoverE(&err).<Method>(args)` — explicit, pure runtime.
- `defer q.RecoverE().<Method>(args)` — zero-arg auto form. The preprocessor rewrites the call to inject `&err` and names the signature's error return when necessary.
Example (auto form):
func doWork() error {
defer q.RecoverE().Map(func(r any) error { return &MyErr{Cause: r} })
riskyPanics()
return nil
}
func (RecoverResult) Err ¶ added in v0.0.15
func (r RecoverResult) Err(replacement error)
Err stores the supplied replacement error on panic, discarding the original panic value and stack.
func (RecoverResult) ErrF ¶ added in v0.0.15
func (r RecoverResult) ErrF(fn func(*PanicError) error)
ErrF transforms the default *PanicError wrapper via fn before storing. Useful when the caller wants to prepend context but still preserve the original panic metadata.
func (RecoverResult) Map ¶ added in v0.0.15
func (r RecoverResult) Map(fn func(any) error)
Map runs fn on the recovered panic value; the returned error is stored via errPtr. Use for custom panic-shape translation.
func (RecoverResult) Wrap ¶ added in v0.0.15
func (r RecoverResult) Wrap(msg string)
Wrap prefixes the default PanicError with msg via fmt.Errorf.
func (RecoverResult) Wrapf ¶ added in v0.0.15
func (r RecoverResult) Wrapf(format string, args ...any)
Wrapf prefixes the default PanicError with a formatted message.
type SQLQuery ¶ added in v0.0.37
SQLQuery pairs a parameterised SQL query string with its corresponding ordered argument list. Produced by q.SQL, q.PgSQL, and q.NamedSQL at compile time. Drop directly into the driver:
s := q.SQL("SELECT * FROM users WHERE id = {id} AND status = {status}")
row := db.QueryRowContext(ctx, s.Query, s.Args...)
func NamedSQL ¶ added in v0.0.37
NamedSQL is q.SQL with `:nameN` placeholders for drivers that support named parameters (sqlx, jmoiron/sqlx-style query helpers). The placeholder names are auto-generated as `:name1`, `:name2`, … in left-to-right order; if you need stable names tied to the source identifiers, hand-write the query.
s := q.NamedSQL("SELECT * FROM users WHERE id = {id}")
// s.Query → "SELECT * FROM users WHERE id = :name1"
// s.Args → []any{id}
func PgSQL ¶ added in v0.0.37
PgSQL is q.SQL with `$1`, `$2`, … (1-indexed) placeholders for PostgreSQL drivers (lib/pq, pgx).
s := q.PgSQL("SELECT * FROM users WHERE id = {id} AND status = {status}")
// s.Query → "SELECT * FROM users WHERE id = $1 AND status = $2"
// s.Args → []any{id, status}
func SQL ¶ added in v0.0.37
SQL builds a parameterised query using `?` placeholders for each `{expr}` segment in the format. The format MUST be a Go string literal — dynamic format strings are rejected at scan time (allowing them would re-open the SQL-injection hole the helper exists to close).
s := q.SQL("SELECT name FROM users WHERE id = {id}")
// s.Query → "SELECT name FROM users WHERE id = ?"
// s.Args → []any{id}
`?` placeholders are the most portable form — SQLite, MySQL, and every database/sql driver that accepts plain positional binds. For Postgres-style numbered placeholders, use q.PgSQL. For named- param drivers (sqlx, etc.), use q.NamedSQL.
Brace-escape `{{` for literal `{` and `}}` for literal `}`.
type Scope ¶ added in v0.0.101
type Scope struct {
// contains filtered or unexported fields
}
Scope is a runtime cache + cleanup container. Holds a typed cache (key = type identity string emitted by the rewriter) and an ordered cleanup list. Close fires every registered cleanup in reverse and is idempotent; Closed reports the state.
Scopes are NOT tied to program startup. Useful patterns:
- Per-request: scope := q.NewScope().BoundTo(reqCtx) — handler- constructed deps share a scope that closes with the request.
- Per-tenant: long-lived scope per tenant, closed on tenant deletion.
- Per-session: opened when a session starts, closed on logout.
- Per-test: opened per test, closed in t.Cleanup.
Components attach to the scope incrementally as the program progresses. Three registration paths cover the surface:
- q.Assemble[T](...).WithScope(s) — the rewriter Commit's freshly- built deps (cache + cleanups) atomically on success.
- scope.Attach(c) / scope.AttachE(c) — bind a Closer / Closer- with-error to the scope. *Scope itself satisfies Closer, so subscopes nest naturally.
- scope.AttachFn(handle, cleanup) — bind a custom closure under a comparable handle for later scope.Detach(handle).
All accessors are safe for concurrent use. Commit and Attach serialise writes under a single lock; a concurrent Close either sees the entire batch or none of it.
func NewScope ¶ added in v0.0.101
func NewScope() *Scope
NewScope constructs a fresh *Scope with no close trigger attached. The caller is expected to chain one of .DeferCleanup() / .NoDeferCleanup() / .BoundTo(ctx) to set the close trigger:
scope := q.NewScope().DeferCleanup() // close on enclosing func return scope, shutdown := q.NewScope().NoDeferCleanup() // caller-managed close scope := q.NewScope().BoundTo(ctx) // close on ctx cancellation
Calling q.NewScope() without a chain terminator is also valid — the caller manages lifetime explicitly via scope.Close() at the right point. The chain is sugar for the three common patterns.
func (*Scope) Attach ¶ added in v0.0.101
Attach binds a Closer (anything with `Close()`) to the scope. scope.Close fires c.Close along with everything else, in reverse registration order.
If the scope is already closed at Attach time, c.Close is fired eagerly and ErrScopeClosed is returned (informationally — the caller does NOT need to fall back; the resource has been disposed). This avoids the leak risk of returning an error and trusting the caller to handle teardown.
*Scope itself satisfies the Closer interface, so subscopes nest naturally:
parent := q.NewScope().BoundTo(ctx) child := q.NewScope() parent.Attach(child) // when parent closes, child closes too. // when child closes independently, it removes itself from // parent's cleanup list so parent doesn't retain it past its // useful lifetime.
Pass c again to scope.Detach to remove the binding before scope.Close runs.
func (*Scope) AttachE ¶ added in v0.0.101
AttachE is Attach for resources whose Close returns an error. Errors from Close at scope-close time (or at eager auto-fire when the scope is already closed) are routed through q.LogCloseErr — the same sink q.Assemble's auto-cleanup uses, so failed teardown is logged structurally rather than silently dropped.
func (*Scope) AttachFn ¶ added in v0.0.101
AttachFn binds a custom cleanup closure under handle's identity. handle must be == comparable (pointer, comparable struct, interface holding such); pass it back to scope.Detach to remove the binding before scope.Close runs.
If the scope is already closed at AttachFn time, cleanup is fired eagerly and ErrScopeClosed is returned (informationally — the resource is already disposed; the caller doesn't need to fall back).
conn := openConnection()
if err := scope.AttachFn(conn, func() { conn.Drain(); conn.Close() }); err != nil {
// scope was closed; conn has already been drained + closed.
return err
}
If handle is a *Scope, AttachFn also wires the auto-detach (independent close removes the entry from this scope) — same shape as Attach when the closer happens to be a *Scope.
A nil handle or nil cleanup is rejected.
func (*Scope) AttachFnE ¶ added in v0.0.101
AttachFnE is AttachFn for cleanup funcs that return an error. The returned error at scope-close time (or at eager auto-fire when the scope is already closed) is routed through q.LogCloseErr.
func (*Scope) BoundTo ¶ added in v0.0.101
BoundTo chains onto a freshly-constructed scope and wires its close to fire when ctx is cancelled (via context.AfterFunc). Returns the same *Scope. Direct scope.Close() remains valid; both paths are idempotent.
scope := q.NewScope().BoundTo(ctx) server := q.Try(q.Assemble[*Server](recipes...).WithScope(scope)) // scope.Close() fires when ctx is cancelled — typical app-shutdown // or per-request pattern.
func (*Scope) Close ¶ added in v0.0.101
func (s *Scope) Close()
Close fires every registered cleanup in reverse registration order. Idempotent and safe for concurrent / recursive use:
- Concurrent: the lock + closed-flag transition guarantees exactly one goroutine runs the body. Other concurrent callers bail immediately (they don't block waiting for the first close to finish — closures are independent units of work and waiting buys nothing).
- Recursive (cleanup calls scope.Close again, directly or via a cycle like A.Attach(B); B.Attach(A); A.Close): the bail check fires before re-entering the body, so no deadlock.
- Mutex is released before firing cleanups so cleanup callbacks can re-enter the scope (Attach / Detach / Load all acquire the lock briefly).
After Close, Load returns (nil, false) for any key, Commit / Attach* still accept input but auto-fire cleanups eagerly (and return ErrScopeClosed informationally), and Detach is a no-op.
If this scope was Attach'd as a child of one or more parent scopes, Close also calls parent.Detach(self) on each so the parents drop their reference to this scope (avoiding a GC retention chain when children close independently).
func (*Scope) Commit ¶ added in v0.0.101
func (s *Scope) Commit(entries []ScopeEntry, child *Scope) error
Commit atomically writes a batch of cache entries and (if non- nil) attaches child as a single cleanup entry under the same lock acquisition. A concurrent Close either sees the whole batch or none of it.
If s is already closed at Commit time, cache entries are dropped and child.Close is fired eagerly so its cleanups run (no leak). ErrScopeClosed is returned informationally — the WithScope IIFE then returns this error to its caller.
The WithScope IIFE pattern: build fresh deps into a per-call internal scope (via internal.AttachE / internal.AttachFn etc.), then call external.Commit(freshCache, internal). Closing external cascades through internal in reverse-attach order so the per-call deps close together, after later-registered scope entries. When child is auto-detached on independent close, this scope's reference to it is removed (no GC retention chain).
child can be nil — Commit then writes only the cache entries. Use that shape for non-resource bulk loads.
func (*Scope) DeferCleanup ¶ added in v0.0.101
DeferCleanup chains onto a freshly-constructed scope. The preprocessor injects a `defer scope.Close()` into the enclosing function so cleanups fire when that function returns. Returns the same *Scope so the result is directly usable.
The runtime body is unreachable in a successful build — bare invocation (rewriter missed it) panics so the gap is loud.
scope := q.NewScope().DeferCleanup() server := q.Try(q.Assemble[*Server](recipes...).WithScope(scope)) // scope.Close() fires when the enclosing function returns.
The chain ONLY works when applied directly to q.NewScope() — the rewriter recognises the literal `q.NewScope().DeferCleanup()` shape. Calling .DeferCleanup() on an existing scope (e.g. one passed in as a parameter) is rejected by the rewriter.
func (*Scope) Detach ¶ added in v0.0.101
Detach removes the cleanup registered under handle's identity. Returns true if found and removed, false if no match (including after scope.Close, which clears all registrations). Detach matches the FIRST cleanup registered under the handle — if a handle was attached more than once, repeated Detach calls peel them off in reverse-registration order.
handle must be == comparable; passing an uncomparable value (slice, map, func) panics — same rule as Go's map keys.
func (*Scope) Load ¶ added in v0.0.101
Load returns the cached value for key, if any, plus ok==true on hit. Returns (nil, false) when the key is absent OR the scope is closed. The WithScope IIFE additionally checks Closed() on a miss to disambiguate "build fresh" from "bail with ErrScopeClosed".
func (*Scope) NoDeferCleanup ¶ added in v0.0.101
NoDeferCleanup chains onto a freshly-constructed scope. Returns the same *Scope plus an idempotent close func wrapping scope.Close. The caller is responsible for firing close.
scope, shutdown := q.NewScope().NoDeferCleanup() defer shutdown() server := q.Try(q.Assemble[*Server](recipes...).WithScope(scope))
Equivalent to q.NewScope() plus calling scope.Close() directly; the close func form composes with patterns like context.AfterFunc(ctx, shutdown) or signal-handler wiring.
type ScopeEntry ¶ added in v0.0.101
ScopeEntry is a fresh-built cache entry staged by a WithScope IIFE for atomic registration via Commit. Cleanups for the freshly-built deps live on the IIFE's internal scope (the child arg to Commit); ScopeEntry carries only the cache write.
type TraceResult ¶ added in v0.0.15
type TraceResult[T any] struct { // contains filtered or unexported fields }
TraceResult carries a captured (value, err) pair for q.TraceE. Every method bubbles with a call-site `file:line` prefix injected by the preprocessor.
func TraceE ¶ added in v0.0.15
func TraceE[T any](v T, err error) TraceResult[T]
TraceE is the chain variant of Trace. Each shape method composes over the location prefix — `q.TraceE(call).Wrap("ctx")` bubbles `"file.go:42: ctx: <inner>"`. Mirrors TryE's vocabulary.
func (TraceResult[T]) Catch ¶ added in v0.0.15
func (r TraceResult[T]) Catch(fn func(error) (T, error)) T
Catch recovers or transforms; the returned error (if any) is still prefixed with the call-site file:line.
func (TraceResult[T]) Err ¶ added in v0.0.15
func (r TraceResult[T]) Err(replacement error) T
Err bubbles the supplied replacement error, still prefixed with the call-site file:line.
func (TraceResult[T]) ErrF ¶ added in v0.0.15
func (r TraceResult[T]) ErrF(fn func(error) error) T
ErrF bubbles fn(capturedErr), prefixed with the call-site file:line.
func (TraceResult[T]) Wrap ¶ added in v0.0.15
func (r TraceResult[T]) Wrap(msg string) T
Wrap is fmt.Errorf("<file>:<line>: <msg>: %w", err) sugar.
func (TraceResult[T]) Wrapf ¶ added in v0.0.15
func (r TraceResult[T]) Wrapf(format string, args ...any) T
Wrapf is fmt.Errorf("<file>:<line>: <format>: %w", args..., err) sugar.
type UnwrapResult ¶ added in v0.0.72
type UnwrapResult[T any] struct { // contains filtered or unexported fields }
UnwrapResult carries a captured (T, error) pair for the q.UnwrapE chain. Methods either return v (when err is nil) or panic with the shaped error.
func UnwrapE ¶ added in v0.0.72
func UnwrapE[T any](v T, err error) UnwrapResult[T]
UnwrapE is the chain variant of Unwrap. The chain methods shape the err before panicking (.Wrap / .Wrapf / .Err / .ErrF) or recover with a fallback value (.Catch). All methods are plain runtime — UnwrapE is NOT rewritten by the preprocessor.
Use it for the same contexts as Unwrap (main, init, tests) when you want richer error context on the panic, a wrapped sentinel for errors.Is detection, or a recovery path:
func main() {
server := q.UnwrapE(q.Assemble[*Server](newConfig, newDB, newServer)).Wrap("server init")
server.Run()
}
// .Catch lets the caller recover instead of panicking.
cfg := q.UnwrapE(loadConfig()).Catch(func(error) (*Config, error) {
return defaultConfig(), nil
})
func (UnwrapResult[T]) Catch ¶ added in v0.0.72
func (r UnwrapResult[T]) Catch(fn func(error) (T, error)) T
Catch passes the captured err through fn when non-nil. fn returns either a recovered (T, nil) — used in place of the panic — or (zero, newErr) — newErr panics in place of the original.
func (UnwrapResult[T]) Err ¶ added in v0.0.72
func (r UnwrapResult[T]) Err(replacement error) T
Err panics with the supplied replacement error when the captured err is non-nil; otherwise returns v.
func (UnwrapResult[T]) ErrF ¶ added in v0.0.72
func (r UnwrapResult[T]) ErrF(fn func(error) error) T
ErrF panics with fn(capturedErr) when err is non-nil; otherwise returns v.
func (UnwrapResult[T]) Wrap ¶ added in v0.0.72
func (r UnwrapResult[T]) Wrap(msg string) T
Wrap panics with fmt.Errorf("<msg>: %w", err) when err is non-nil; otherwise returns v.
func (UnwrapResult[T]) Wrapf ¶ added in v0.0.72
func (r UnwrapResult[T]) Wrapf(format string, args ...any) T
Wrapf panics with fmt.Errorf(format + ": %w", args..., err) when err is non-nil; otherwise returns v.
type ValidatedStruct ¶ added in v0.0.92
type ValidatedStruct struct{}
ValidatedStruct is the general-purpose sibling of FnParams. The validation semantics are identical — every named field must be keyed in literals unless tagged `q:"optional"` (or `q:"opt"`). The two markers exist so users can pick the name that reads best at the use site:
- FnParams — for function-parameter structs (typical use case).
- ValidatedStruct — for any other struct where literal construction should fail-loud on missing fields (DTOs, configuration objects, model structs, builder internals).
Build-failure example:
type Config struct {
_ q.ValidatedStruct
Name string // required
Version int // required
Logger any `q:"opt"` // optional
}
func main() {
cfg1 := Config{Name: "app", Version: 1} // OK
cfg2 := Config{Name: "app"} // ✗ build error:
// // q.ValidatedStruct: required field(s) [Version]
// // not set in Config literal
use(cfg1, cfg2)
}
Pick whichever name reads better at your call sites; the preprocessor accepts both.
Plain runtime type — NOT rewritten by the preprocessor.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package either ships the Scala-flavoured 2-arm sum type: either.Either[L, R] holds exactly one of L (the "left" arm) or R (the "right" arm).
|
Package either ships the Scala-flavoured 2-arm sum type: either.Either[L, R] holds exactly one of L (the "left" arm) or R (the "right" arm). |