Skip to content
/ xrun Public

Type-safe result extraction for Go's callback-based APIs using generics.

License

Notifications You must be signed in to change notification settings

knwoop/xrun

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

xrun

Type-safe result extraction for Go's callback-based APIs using generics.

Problem

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 attempt

Solution

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

Install

go get github.com/knwoop/xrun

API

Executor

type Executor func(ctx context.Context, f func(ctx context.Context) error) error

Abstracts any "run a function within some context" pattern — transactions, locks, retries, circuit breakers, etc.

Do[T]

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.

Compose

func Compose(executors ...Executor) Executor

Chains 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)
})

Use Cases

  • Spanner / database transactions — type-safe result extraction with automatic retry safety
  • Distributed locks — wrap lock acquire/release around business logic
  • Retry loops — combine with Do to 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

License

MIT

About

Type-safe result extraction for Go's callback-based APIs using generics.

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages