Comment by atombender

Comment by atombender 20 hours ago

0 replies

It's not required, but eschewing it ends up going against the grain, since so much of the ecosystem is written to use contexts, including the standard library.

For example, say you instead of contexts, you use channels for cancellation. You can have a goroutine like this:

    go func() {
      for {
        select {
        case <-stop:
          return
        case <-time.After(1*time.Second):
          resp := fetchURL(url)
          processResult(resp.Body)  // Simplified, of course
        }
      }
    }()
If you want to be able to shut this goroutine down gracefully, you're going to have an issue where http.Get() may stall for a long time, preventing the goroutine from quitting.

Likewise, processResult() may be doing stuff that cannot be aborted just by closing the stop channel. You could pass the stop channel to it, but now you're just reinventing contexts.

Of course, you can choose to only use contexts where you're forced to, and invent wrappers around standard library stuff (e.g. the HTTP client), but at that point you're going pretty far to avoid them.

I do think the context is problematic. For the purposes of cancellation, it's invasive and litters the call graph with parameters and variables. Goroutines really ought to have an implicit context inherited from its parent, since everything is using it anyway.

Contexts are wildly abused for passing data around, leading to bloated contexts and situations where you can't follow the chain of data-passing without carefully reviewing the entire call graph. I always recommend not being extremely discriminating about where to pass values in context. A core principle is that it has to be something that is so pervasive that it would be egregious to pass around explicitly, such as loggers and application-wide feature flags.