q

package
v0.0.137 Latest Latest
Warning

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

Go to latest
Published: Apr 29, 2026 License: MIT Imports: 20 Imported by: 0

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

Constants

This section is empty.

Variables

View Source
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.

View Source
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.

View Source
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.

View Source
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.

View Source
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.

View Source
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.

View Source
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.

View Source
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

func AllFields[T any]() []string

AllFields returns the names of every field on T, including unexported ones, in source declaration order.

func As added in v0.0.15

func As[T any](x any) T

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

func AsOneOf[T any](v any) T

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

func AssemblyDebugWriter(ctx context.Context) io.Writer

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

func AtCompileTime[R any](fn func() R, codec ...Codec[R]) R

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

func AtCompileTimeCode[R any](fn func() string) R

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

func Await[T any](f Future[T]) T

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

func AwaitAll[T any](futures ...Future[T]) []T

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

func AwaitAllCtx[T any](ctx context.Context, futures ...Future[T]) []T

AwaitAllCtx is AwaitAll with context cancellation; the rewriter emits q.AwaitAllRawCtx as the inner helper.

func AwaitAllRaw added in v0.0.20

func AwaitAllRaw[T any](futures ...Future[T]) ([]T, error)

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

func AwaitAllRawCtx[T any](ctx context.Context, futures ...Future[T]) ([]T, error)

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

func AwaitAny[T any](futures ...Future[T]) T

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

func AwaitAnyCtx[T any](ctx context.Context, futures ...Future[T]) T

AwaitAnyCtx is AwaitAny with context cancellation.

func AwaitAnyRaw added in v0.0.20

func AwaitAnyRaw[T any](futures ...Future[T]) (T, error)

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

func AwaitAnyRawCtx[T any](ctx context.Context, futures ...Future[T]) (T, error)

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

func AwaitCtx[T any](ctx context.Context, f Future[T]) T

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

func AwaitRaw[T any](f Future[T]) (T, error)

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

func AwaitRawCtx[T any](ctx context.Context, f Future[T]) (T, error)

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

func Camel(s string) string

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

func CheckCtx(ctx context.Context)

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

func Chunk[T any](slice []T, n int) [][]T

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

func Const[T any](v T) func(error) (T, error)

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

func Count[T any](slice []T, pred func(T) bool) int

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

func Deadline(ctx context.Context, t time.Time) context.Context

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

func DebugPrintlnAt[T any](label string, v T) T

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

func DebugSlogAttr[T any](v T) slog.Attr

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

func DrainAllCtx[T any](ctx context.Context, chans ...<-chan T) [][]T

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

func DrainAllRawCtx[T any](ctx context.Context, chans ...<-chan T) ([][]T, error)

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

func DrainCtx[T any](ctx context.Context, ch <-chan T) []T

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

func DrainRawCtx[T any](ctx context.Context, ch <-chan T) ([]T, error)

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

func Drop[T any](slice []T, n int) []T

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

func Exists[T any](slice []T, pred func(T) bool) bool

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

func ExistsErr[T any](slice []T, pred func(T) (bool, error)) (bool, error)

ExistsErr is Exists with a fallible predicate. First error short-circuits ahead of any "found" decision.

func Expr added in v0.0.29

func Expr[T any](v T) string

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

func F(format string) string

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

func Ferr(format string) error

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

func Fields[T any]() []string

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

func Filter[T any](slice []T, pred func(T) bool) []T

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

func FilterErr[T any](slice []T, pred func(T) (bool, error)) ([]T, error)

FilterErr is Filter with a fallible predicate. First error short-circuits.

func Find added in v0.0.47

func Find[T any](slice []T, pred func(T) bool) (T, bool)

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

func FlatMapErr[T, R any](slice []T, fn func(T) ([]R, error)) ([]R, error)

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

func FoldErr[T, R any](slice []T, init R, fn func(R, T) (R, error)) (R, error)

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

func ForAll[T any](slice []T, pred func(T) bool) bool

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

func ForAllErr[T any](slice []T, pred func(T) (bool, error)) (bool, error)

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

func ForEachErr[T any](slice []T, fn func(T) error) error

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

func Generator[T any](body func()) iter.Seq[T]

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

func GetPar(ctx context.Context) int

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

func InstallSlog(base slog.Handler)

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

func Kebab(s string) string

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

func Lock(l sync.Locker)

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

func LogCloseErr(err error, recipe string)

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

func Lower(s string) string

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

func MapErr[T, R any](slice []T, fn func(T) (R, error)) ([]R, error)

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

func Match[R any](value any, arms ...MatchArm[R]) R

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

func Max[T cmp.Ordered](slice []T) (T, bool)

Max returns the largest element of slice. Empty input returns (zero, false).

func MaxBy added in v0.0.53

func MaxBy[T any, K cmp.Ordered](slice []T, fn func(T) K) (T, bool)

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

func Min[T cmp.Ordered](slice []T) (T, bool)

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

func MinBy[T any, K cmp.Ordered](slice []T, fn func(T) K) (T, bool)

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

func Ok[T any](v T, ok bool) T

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

func ParExists[T any](ctx context.Context, slice []T, pred func(T) bool) bool

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

func ParFilter[T any](ctx context.Context, slice []T, pred func(T) bool) []T

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

func ParFlatMap[T, R any](ctx context.Context, slice []T, fn func(T) []R) []R

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

func ParForAll[T any](ctx context.Context, slice []T, pred func(T) bool) bool

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

func ParForEach[T any](ctx context.Context, slice []T, fn func(T))

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

func ParForEachErr[T any](ctx context.Context, slice []T, fn func(context.Context, T) error) error

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

func ParMap[T, R any](ctx context.Context, slice []T, fn func(T) R) []R

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

func Partition[T any](slice []T, pred func(T) bool) ([]T, []T)

Partition splits slice into (matching, nonMatching) by pred. Both slices preserve input order. Allocates two new slices.

func Pascal added in v0.0.42

func Pascal(s string) string

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

func RecvAnyCtx[T any](ctx context.Context, chans ...<-chan T) T

RecvAnyCtx is RecvAny with ctx cancellation.

func RecvAnyRaw added in v0.0.20

func RecvAnyRaw[T any](chans ...<-chan T) (T, error)

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

func RecvAnyRawCtx[T any](ctx context.Context, chans ...<-chan T) (T, error)

RecvAnyRawCtx is RecvAnyRaw with ctx cancellation. If ctx fires before any channel delivers, returns (zero, ctx.Err()).

func RecvCtx added in v0.0.20

func RecvCtx[T any](ctx context.Context, ch <-chan T) T

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

func RecvRawCtx[T any](ctx context.Context, ch <-chan T) (T, error)

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

func Require(cond bool, msg ...string)

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

func SlogAttr[T any](v T) slog.Attr

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

func SlogContextHandler(base slog.Handler) slog.Handler

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

func SlogCtx(ctx context.Context, attrs ...slog.Attr) context.Context

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

func SlogFile() slog.Attr

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

func SlogFileLine() slog.Attr

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

func SlogLine() slog.Attr

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

func Snake(s string) string

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

func Sort[T cmp.Ordered](slice []T) []T

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

func SortBy[T any, K cmp.Ordered](slice []T, fn func(T) K) []T

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

func SortFunc[T any](slice []T, less func(a, b T) int) []T

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

func Sum[T cmp.Ordered](slice []T) T

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

func Tag[T any](field, key string) string

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

func Take[T any](slice []T, n int) []T

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

func Tern[T any](cond bool, ifTrue, ifFalse T) T

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

func Timeout(ctx context.Context, dur time.Duration) context.Context

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

func Title(s string) string

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

func ToErr[T any, E any, P interface {
	*E
	error
}](v T, e P) (T, error)

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

func Trace[T any](v T, err error) T

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

func Try[T any](v T, err error) T

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

func TypeName[T any]() string

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

func Unwrap[T any](v T, err error) T

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

func Unzip[A, B any](pairs []Pair[A, B]) ([]A, []B)

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

func Upper(s string) string

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

func WithAssemblyDebug(ctx context.Context) context.Context

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

func WithAssemblyDebugWriter(ctx context.Context, w io.Writer) context.Context

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

func WithPar(ctx context.Context, limit int) context.Context

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

func WithParUnbounded(ctx context.Context) context.Context

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

func AtomOf[T ~string]() Atom

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

type Codec[T any] interface {
	Encode(v T) ([]byte, error)
	Decode(data []byte, v *T) error
}

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

func BinaryCodec[T any]() Codec[T]

BinaryCodec returns the encoding/binary codec for T. Fixed-size types only (no slices, maps, strings) — produces the smallest possible output.

func GobCodec added in v0.0.56

func GobCodec[T any]() Codec[T]

GobCodec returns the gob codec for T. Handles unexported fields when the type is registered with gob.Register; output is larger than JSON but more flexible.

func JSONCodec added in v0.0.56

func JSONCodec[T any]() Codec[T]

JSONCodec returns the JSON codec for T (the default for q.AtCompileTime). Lossy on unexported fields — use GobCodec for those.

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

func Coro[I, O any](body func(in <-chan I, out chan<- O)) *Coroutine[I, O]

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

func (c *Coroutine[I, O]) Done() bool

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

func (c *Coroutine[I, O]) Resume(v I) (O, bool)

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).

func (*Coroutine[I, O]) Wait added in v0.0.52

func (c *Coroutine[I, O]) Wait()

Wait blocks until the body's goroutine has returned. Useful in tests + when the caller needs a hard barrier between "I'm done using the coroutine" and "the body is fully torn down."

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

func AwaitAllCtxE[T any](ctx context.Context, futures ...Future[T]) ErrResult[[]T]

AwaitAllCtxE is the chain variant of AwaitAllCtx.

func AwaitAllE added in v0.0.20

func AwaitAllE[T any](futures ...Future[T]) ErrResult[[]T]

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

func AwaitAnyCtxE[T any](ctx context.Context, futures ...Future[T]) ErrResult[T]

AwaitAnyCtxE is the chain variant of AwaitAnyCtx.

func AwaitAnyE added in v0.0.20

func AwaitAnyE[T any](futures ...Future[T]) ErrResult[T]

AwaitAnyE is the chain variant of AwaitAny.

func AwaitCtxE added in v0.0.20

func AwaitCtxE[T any](ctx context.Context, f Future[T]) ErrResult[T]

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

func AwaitE[T any](f Future[T]) ErrResult[T]

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

func DrainAllCtxE[T any](ctx context.Context, chans ...<-chan T) ErrResult[[]T]

DrainAllCtxE is the chain variant of DrainAllCtx.

func DrainCtxE added in v0.0.20

func DrainCtxE[T any](ctx context.Context, ch <-chan T) ErrResult[[]T]

DrainCtxE is the chain variant of DrainCtx.

func RecvAnyCtxE added in v0.0.20

func RecvAnyCtxE[T any](ctx context.Context, chans ...<-chan T) ErrResult[T]

RecvAnyCtxE is the chain variant of RecvAnyCtx.

func RecvAnyE added in v0.0.20

func RecvAnyE[T any](chans ...<-chan T) ErrResult[T]

RecvAnyE is the chain variant of RecvAny.

func RecvCtxE added in v0.0.20

func RecvCtxE[T any](ctx context.Context, ch <-chan T) ErrResult[T]

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

func TryE[T any](v T, err error) ErrResult[T]

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

func (r ErrResult[T]) Catch(fn func(error) (T, error)) T

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

func (r ErrResult[T]) Err(replacement error) T

Err bubbles the supplied constant error when the captured err is non-nil. The original err is discarded.

func (ErrResult[T]) ErrF

func (r ErrResult[T]) ErrF(fn func(error) error) T

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

func (r ErrResult[T]) RecoverAs(typedNil error, value T) ErrResult[T]

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

func (r ErrResult[T]) RecoverIs(sentinel error, value T) ErrResult[T]

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

func (r ErrResult[T]) Wrap(msg string) T

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

func (r ErrResult[T]) Wrapf(format string, args ...any) T

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.

func Async added in v0.0.15

func Async[T any](fn func() (T, error)) Future[T]

Async spawns fn in a goroutine and returns a Future that q.Await or q.AwaitRaw can pull the result from. Plain runtime function — not rewritten by the preprocessor. Usable standalone.

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

func Sealed[I any](variants ...any) GenMarker

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

func Lazy[T any](v T) *LazyValue[T]

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

func LazyFromThunk[T any](thunk func() T) *LazyValue[T]

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.

func (*LazyValue[T]) IsForced added in v0.0.88

func (l *LazyValue[T]) IsForced() bool

IsForced reports whether the underlying thunk has been evaluated. Diagnostic only — observing IsForced does not race with Value, but callers that act on the result race with concurrent first-call users in the obvious way.

func (*LazyValue[T]) Value added in v0.0.88

func (l *LazyValue[T]) Value() T

Value evaluates the wrapped thunk on the first call (under sync.Once) and returns the cached value on every subsequent call. Concurrent first-call races resolve to a single thunk execution.

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

func Case[R any](cond any, result R) MatchArm[R]

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

func Default[R any](result R) MatchArm[R]

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

func OnType[R, T any](handler func(T) R) MatchArm[R]

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

func NotNilE[T any](p *T) NilResult[T]

NotNilE wraps a *T into a NilResult for chain-style nil handling. See NotNil for the bare bubble form.

func (NilResult[T]) Catch

func (r NilResult[T]) Catch(fn func() (*T, error)) *T

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]) Err

func (r NilResult[T]) Err(replacement error) *T

Err bubbles the supplied constant error when the captured pointer is nil.

func (NilResult[T]) ErrF

func (r NilResult[T]) ErrF(fn func() error) *T

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

func (r NilResult[T]) Wrap(msg string) *T

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

func (r NilResult[T]) Wrapf(format string, args ...any) *T

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

func AsE[T any](x any) OkResult[T]

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

func OkE[T any](v T, ok bool) OkResult[T]

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

func RecvE[T any](ch <-chan T) OkResult[T]

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

func (r OkResult[T]) Catch(fn func() (T, error)) T

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

func (r OkResult[T]) Err(replacement error) T

Err bubbles the supplied constant error when the captured ok is false.

func (OkResult[T]) ErrF added in v0.0.15

func (r OkResult[T]) ErrF(fn func() error) T

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

func (r OkResult[T]) Wrap(msg string) T

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

func (r OkResult[T]) Wrapf(format string, args ...any) T

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

type OneOf2[A, B any] struct {
	Tag   uint8
	Value any
}

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

type OneOf3[A, B, C any] struct {
	Tag   uint8
	Value any
}

OneOf3 is the ternary discriminated union over arm types A, B, C. Tag is 1 for A, 2 for B, 3 for C.

type OneOf4 added in v0.0.96

type OneOf4[A, B, C, D any] struct {
	Tag   uint8
	Value any
}

OneOf4 is the 4-way discriminated union. See q.OneOf2 for details.

type OneOf5 added in v0.0.96

type OneOf5[A, B, C, D, E any] struct {
	Tag   uint8
	Value any
}

OneOf5 is the 5-way discriminated union. See q.OneOf2 for details.

type OneOf6 added in v0.0.96

type OneOf6[A, B, C, D, E, F any] struct {
	Tag   uint8
	Value any
}

OneOf6 is the 6-way discriminated union. See q.OneOf2 for details.

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).

func Zip added in v0.0.51

func Zip[A, B any](as []A, bs []B) []Pair[A, B]

Zip pairs the elements of as and bs in input order. The output length is min(len(as), len(bs)) — extra elements of the longer slice are dropped. Allocates a fresh slice.

pairs := q.Zip(names, ages)
for _, p := range pairs {
    fmt.Println(p.First, p.Second)
}

type PanicError added in v0.0.15

type PanicError struct {
	Value any
	Stack []byte
}

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

func At[T any](expr T) PathChain[T]

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

func (c PathChain[T]) OrE(v T, err error) T

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

func (c PathChain[T]) OrElse(alt T) PathChain[T]

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

func (c PathChain[T]) OrError(err error) T

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).

func (PathChain[T]) OrZero added in v0.0.86

func (c PathChain[T]) OrZero() T

OrZero terminates the chain with the zero value of T. Returns the first non-nil path's value, or T's zero value when every path is nil. Useful when the natural fallback is the empty / zero state.

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

type SQLQuery struct {
	Query string
	Args  []any
}

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

func NamedSQL(format string) SQLQuery

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

func PgSQL(format string) SQLQuery

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

func SQL(format string) SQLQuery

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

func (s *Scope) Attach(c interface{ Close() }) error

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

func (s *Scope) AttachE(c interface{ Close() error }) error

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

func (s *Scope) AttachFn(handle any, cleanup func()) error

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

func (s *Scope) AttachFnE(handle any, cleanup func() error) error

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

func (s *Scope) BoundTo(ctx context.Context) *Scope

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) Closed added in v0.0.101

func (s *Scope) Closed() bool

Closed reports whether Close has been called.

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

func (s *Scope) DeferCleanup() *Scope

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

func (s *Scope) Detach(handle any) bool

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

func (s *Scope) Load(key string) (any, bool)

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

func (s *Scope) NoDeferCleanup() (*Scope, func())

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

type ScopeEntry struct {
	Key   string
	Value any
}

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.

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).

Jump to

Keyboard shortcuts

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