I wonder why. It's documented [1] but seems strange to me. It enforces that errgroup is single-use (rather than cyclical), but I might do that by outright panicking if Go is called after Wait returns. Goroutines in the group might launch others in the group, but presumably not after they've finished, so I'd expect after Wait returns that there are no more calls to Go. Unless folks are passing the group to existing goroutines and then those launch more tasks in the group? That seems like an undesirable pattern.
[1] though not on Wait, just on WithContext: https://pkg.go.dev/golang.org/x/sync/errgroup#WithContext
Why those other go routines would spawn processes that they don't clean up otherwise? They probably shouldn't, but canceling the context should always be safe, and is more likely to safely handle what otherwise would be a bug, than it is to cause a bug.
They gave an example of a bug it caused, and I haven't seen any examples of bugs it would have prevented, so I'm not sure I agree.
Suppose that you spawn a Goroutine for handling an HTTP request from client A. While the request is being processed (e.g. calling DB, external services, etc.), client A drops the TCP connection. With contexts we can notify the handler to cancel any ongoing/pending task. Otherwise, the handler will be still running the remaining tasks, hence wasting resources.
Note that if we pass a context to a function, it entirely depends on the function as to when or whether it will cancel its tasks. It's a cooperative multitasking after all.
In Rust, it's easy to cancel a future: Just drop it. It's not that the Rust's approach is perfect. Rust has the opposite problem: Since a future can be dropped anytime (in any await point), dropping a future in the middle of an execution might lead to an inconsistent state, if the future isn't properly implemented.
It can be any blocking API, not just IO, for example you can acquire semaphore.Weighted in a cancellable way. The API has to be designed to support context cancellation, and most of the standard library is.
Important parts of the standard library are not designed for contexts. Let's say I want to write data to a file and then cancel that. How do I do that? `os.File.Write`, `io.Copy`, `ioutil.WriteFile`... none of these let me cancel a write.
The `io.Writer` interface not supporting contexts also means things like the compress and archive stdlib packages don't support contexts, among several others.
Most of the rest of it has had contexts bolted on in nonstandard ways. If you want to use contexts, you can no longer use "net.Dial" or "net.DialTimeout", but must instead use the awkward "(&net.Dialer{}).DialContext" method. The http package has similar issues, including non-idiomatic context usage.
The go stdlib was mostly built before contexts existed, has promised backwards compatibility, and it really shows.
Contexts are how you address that.
It's a performance nightmare when you put too many layers on it, but to some degree: meh. If that's your performance bottleneck, you have many options.
[1]: In that it's request-local and explicit, which makes it both clearer and much easier to control. For pure "optimize for fewer memory movements or cross-thread locks" purposes, go has nothing, you have to trust the runtime to schedule efficiently. Except maybe runtime.LockOSThread().
[2]: You can always make a new context that's not cancelled, or just not look at the Done channel. But by accident you get better cancellation behavior than e.g. java's thread interruption, because everyone still uses `catch (Exception e) { println(e) }` despite decades of education to not do so.
The Go blog has always good insights and details around things like this: https://go.dev/blog/context
In our work, we make use of contexts for logging and tracing. When a request comes in, we updated the context to contain a request ID, any logs we perform in our functions make use of this context to extract that information in order to connect related logs.
Tracing also makes use of these contexts. When you create a new trace, you wrap a context. That context contains the trace parent. That way, any new traces made using that context, will be linked to the parent
But that's the limit. It's just a processing context, not processing data
So yeah if you have a bunch of different tasks you want to do and you want to be able to stop all of them at once, you want to use a context.