package contextutil import "context" // A problematic situation when implementing context in a function // is when that function has more than one return values // if function has only one return value we can safely wrap it something like this /* func DoSomething() error {} ch := make(chan error) go func() { ch <- DoSomething() }() select { case err := <-ch: // handle error case <-ctx.Done(): // handle context cancelation } */ // but what if we have more than one value to return? // we can use generics and a struct and that is what we are doing here // here we use struct and generics to store return values of a function // instead of storing it in a []interface{} type twoValueCtx[T1 any, T2 any] struct { var1 T1 var2 T2 } type threeValueCtx[T1 any, T2 any, T3 any] struct { var1 T1 var2 T2 var3 T3 } // ExecFunc implements context for a function which has no return values // and executes that function. if context is cancelled before function returns // it will return context error otherwise it will return nil func ExecFunc(ctx context.Context, fn func()) error { ch := make(chan struct{}) go func() { fn() ch <- struct{}{} }() select { case <-ch: return nil case <-ctx.Done(): return ctx.Err() } } // ExecFuncWithTwoReturns wraps a function which has two return values given that last one is error // and executes that function in a goroutine there by implementing context // if context is cancelled before function returns it will return context error // otherwise it will return function's return values func ExecFuncWithTwoReturns[T1 any](ctx context.Context, fn func() (T1, error)) (T1, error) { ch := make(chan twoValueCtx[T1, error]) go func() { x, y := fn() ch <- twoValueCtx[T1, error]{var1: x, var2: y} }() select { case <-ctx.Done(): var tmp T1 return tmp, ctx.Err() case v := <-ch: return v.var1, v.var2 } } // ExecFuncWithThreeReturns wraps a function which has three return values given that last one is error // and executes that function in a goroutine there by implementing context // if context is cancelled before function returns it will return context error // otherwise it will return function's return values func ExecFuncWithThreeReturns[T1 any, T2 any](ctx context.Context, fn func() (T1, T2, error)) (T1, T2, error) { ch := make(chan threeValueCtx[T1, T2, error]) go func() { x, y, z := fn() ch <- threeValueCtx[T1, T2, error]{var1: x, var2: y, var3: z} }() select { case <-ctx.Done(): var tmp1 T1 var tmp2 T2 return tmp1, tmp2, ctx.Err() case v := <-ch: return v.var1, v.var2, v.var3 } }