var errClosed = errors.New("closed")
func doAThing[T any](ctx context.Context, c chan T) (T, error) {
// the generics are not necessary, but they're also not awful for
// DRYing up this type of a select.
select {
case <-ctx.Done():
var empty T
return empty, fmt.Errorf("could not doAThing: %w", ctx.Err())
case item, ok := <-c:
if !ok {
var empty T
return empty, fmt.Errorf("channel of type %T is done: %w", empty, errClosed)
}
return item, nil
}
}
// ... and later...
for {
item, err := doAThing(ctx, channel)
if err != nil {
break
}
// code goes here
}
I dislike labeled loops, naked returns, and else statements, so this is the pattern I use.