Type-safe result extraction for Go's callback-based APIs using generics.
Go libraries commonly use the "Execute Around" pattern:
err := tx.RunInTransaction(ctx, func(ctx context.Context) error {
// ...
return nil
})Extracting results requires declaring variables outside the closure. When the callback is retried (e.g. Spanner transaction aborts), stale values can linger:
var result *Result
err := tx.RunInTransaction(ctx, func(ctx context.Context) error {
r, err := doSomething(ctx)
if r.IsDone() {
result = r // On retry, if condition is false, stale result remains
}
return err
})
// result may hold a value from a failed attemptxrun.Do scopes the result inside the executor, so retries always overwrite:
result, err := xrun.Do(ctx, tx.RunInTransaction, func(ctx context.Context) (*Result, error) {
return doSomething(ctx)
})
// result is always from the last (successful) attemptgo get github.com/knwoop/xrun
type Executor func(ctx context.Context, f func(ctx context.Context) error) errorAbstracts any "run a function within some context" pattern — transactions, locks, retries, circuit breakers, etc.
func Do[T any](ctx context.Context, exec Executor, f func(ctx context.Context) (T, error)) (T, error)Runs f inside the Executor and returns the result in a type-safe way. On retry, the result is naturally overwritten by the latest call to f.
func Compose(executors ...Executor) ExecutorChains multiple Executors left-to-right (first = outermost):
composed := xrun.Compose(withLock, withTransaction)
// equivalent to: withLock(ctx, func(ctx) { withTransaction(ctx, f) })
result, err := xrun.Do(ctx, composed, func(ctx context.Context) (Row, error) {
return queryRow(ctx)
})- Spanner / database transactions — type-safe result extraction with automatic retry safety
- Distributed locks — wrap lock acquire/release around business logic
- Retry loops — combine with
Doto get the result of the last successful attempt - Circuit breakers — compose with other executors for layered resilience
- Composition — stack multiple concerns (lock + transaction + retry) with
Compose