This is about an explicit argument of type "Context". I'm not a Go user, and at first I thought it was about something else: an implicit context variable that allows you to pass stuff deep down the call stack, without intermediate functions knowing about it.
React has "Context", SwiftUI has "@Environment", Emacs LISP has dynamic scope (so I heard). C# has AsyncLocal, Node.JS AsyncLocalStorage.
This is one of those ideas that at first seem really wrong (isn't it just a global variable in disguise?) but is actually very useful and can result in cleaner code with less globals or less superfluous function arguments. Imagine passing a logger like this, or feature flags. Or imagine setting "debug = True" before a function, and it applies to everything down the call stack (but not in other threads/async contexts).
Implicit context (properly integrated into the type system) is something I would consider in any new language. And it might also be a solution here (altough I would say such a "clever" and unusual feature would be against the goals of Go).
> If you use ctx.Value in my (non-existent) company, you’re fired
This is such a bad take.
ctx.Value is incredibly useful for passing around context of api calls. We use it a lot, especially for logging such context values as locales, ids, client info, etc. We then use these context values when calling other services as headers so they gain the context around the original call too. Loggers in all services pluck out values from the context automatically when a log entry is created. It's a fantastic system and serves us well. e.g.
As others have already mentioned, there won't be a Go 2. Besides, I really don't want another verbose method for cancellation; error handling is already bad enough.
Contexts in Go are generally used for convenience in request cancellation, but they're not required, and they're not the only way to do it. Under the hood, a context is just a channel that's closed on cancellation. The way it was done before contexts was pretty much the same:
func CancellableOp(done chan error /* , args... */) {
for {
// ...
// cancellable code:
select {
case <-something:
// ...
case err := <-done:
// log error or whatever
}
}
}
Some compare context "virus" to async virus in languages that bolt-on async runtime on top of sync syntax - but the main difference is you can compose context-aware code with context-oblivious code (by passing context.Background()), and vice versa with no problems. E.g. here's a context-aware wrapper for the standard `io.Reader` that is completely compatible with `io.Reader`:
type ioContextReader struct {
io.Reader
ctx context.Context
}
func (rc ioContextReader) Read(p []byte) (n int, err error) {
done := make(chan struct{})
go func() {
n, err = rc.Reader.Read(p)
close(done)
}()
select {
case <-rc.ctx.Done():
return 0, rc.ctx.Err()
case <-done:
return n, err
}
}
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
rc := ioContextReader{Reader: os.Stdin, ctx: ctx}
// we can use rc in io.Copy as it is an io.Reader
_, err := io.Copy(os.Stdout, rc)
if err != nil {
log.Println(err)
}
}
For io.ReadCloser, we could call `Close()` method when context exits, or even better, with `context.AfterFunc(ctx, rc.Close)`.
Contexts definitely have flaws - verbosity being the one I hate the most - but having them behave as ordinary values, just like errors, makes context-aware code more understandable and flexible.
And just like errors, having cancellation done automatically makes code more prone to errors. When you don't put "on-cancel" code, your code gets cancelled but doesn't clean up after itself. When you don't select on `ctx.Done()` your code doesn't get cancelled at all, making the bug more obvious.
> This probably doesn’t happen often, but it’s prone to name collisions.
It's funny, it really was just using strings as keys until quite recently, and obviously there were collisions and there was no way to "protect" a key/value, etc.
Now the convention is to use a key with a private type, so no more collisions. The value you get is still untyped and needs to be cast, though. Also there are still many older libraries still uses strings.
captainmuon ·2 hours ago
React has "Context", SwiftUI has "@Environment", Emacs LISP has dynamic scope (so I heard). C# has AsyncLocal, Node.JS AsyncLocalStorage.
This is one of those ideas that at first seem really wrong (isn't it just a global variable in disguise?) but is actually very useful and can result in cleaner code with less globals or less superfluous function arguments. Imagine passing a logger like this, or feature flags. Or imagine setting "debug = True" before a function, and it applies to everything down the call stack (but not in other threads/async contexts).
Implicit context (properly integrated into the type system) is something I would consider in any new language. And it might also be a solution here (altough I would say such a "clever" and unusual feature would be against the goals of Go).
Show replies
kalekold ·1 hours ago
This is such a bad take.
ctx.Value is incredibly useful for passing around context of api calls. We use it a lot, especially for logging such context values as locales, ids, client info, etc. We then use these context values when calling other services as headers so they gain the context around the original call too. Loggers in all services pluck out values from the context automatically when a log entry is created. It's a fantastic system and serves us well. e.g.
Show replies
rednafi ·2 hours ago
As others have already mentioned, there won't be a Go 2. Besides, I really don't want another verbose method for cancellation; error handling is already bad enough.
Show replies
bheadmaster ·2 hours ago
Contexts definitely have flaws - verbosity being the one I hate the most - but having them behave as ordinary values, just like errors, makes context-aware code more understandable and flexible.
And just like errors, having cancellation done automatically makes code more prone to errors. When you don't put "on-cancel" code, your code gets cancelled but doesn't clean up after itself. When you don't select on `ctx.Done()` your code doesn't get cancelled at all, making the bug more obvious.
Show replies
the_gipsy ·2 hours ago
It's funny, it really was just using strings as keys until quite recently, and obviously there were collisions and there was no way to "protect" a key/value, etc.
Now the convention is to use a key with a private type, so no more collisions. The value you get is still untyped and needs to be cast, though. Also there are still many older libraries still uses strings.
Show replies