Although the context
package has seen some controversies, it's still one of my favorite features.
Here's an example usage that I've used a few times at Shopify that's turned out to be useful but also a good intro example to this package.
Say, you have a function that runs an infinite loop, e.g. a control loop or a web server:
var someInteger int = 0
func InfiniteLoop(ctx context.Context) {
for {
someInteger = someInteger + 1
select {
case <-ctx.Done():
return
}
}
}
And then you call it with a dummy context in your main
and curse the Gopher gods because they're making you write boilerplate...
func main() {
InfiniteLoop(context.Background())
}
Fair enough, but how do you reliably test your function? This is where I think the separation of context
's interface and the variety of different context types becomes a handy tool:
func TestRunInfiniteLoopOnlyOnce() {
ctx, shutdown := context.WithCancel(context.Background())
shutdown()
InfiniteLoop(ctx)
assert(someInteger == 1)
}
What if you wanted your test to terminate after some event? Just call the shutdown
function when you see your event ;)
What if you wanted your test to run your loop for a few seconds? Just pass a context.WithDeadline
And many more creative what ifs that lie in this package..
The right tool for the right job and all that.. My philosophy is that if something solves your problem pragmatically, then use it. Of course, we could have signaled cancellation through other means (a channel, variable, etc) but I think that the context package gives a nice consistent interface for doing a wide variety of usages.
In conclusion, I ❤️ Go, with all its imperfections.