logoalt Hacker News

Context should go away for Go 2 (2017)

130 pointsby hiohio01/21/2025166 commentsview on HN

Comments

captainmuon01/21/2025

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).

show 14 replies
kalekold01/21/2025

> 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.

    log.WithContext(ctx).Errorf("....", err)
show 4 replies
rednafi01/21/2025

This article is from 2017!

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 1 reply
bheadmaster01/21/2025

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.

show 4 replies
the_gipsy01/21/2025

> 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.

show 1 reply
mukunda_johnson01/21/2025

> It’s very similar to thread-local storage. We know how bad of an idea thread-local storage is. Non-flexible, complicates usage, composition, testing.

I kind of do wish we had goroutine local storage though :) Passing down the context of the request everywhere is ugly.

show 4 replies
nickcw01/21/2025

Contexts implement the idea of cancellation along with go routine local storage and at that they work very well.

What if for the hypothetical Go 2 we add an implicit context for each goroutine. You'd probably need to call a builtin, say `getctx()` to get it.

The context would be inherited by all go routines automatically. If you wanted to change the context then you'd use another builtin `setctx()` say.

This would have the usefulness of the current context without having to pass it down the call chain everwhere.

The cognitive load is two bultins getctx() and setctx(). It would probably be quite easy to implement too - just stuff a context.Context in the G.

alkonaut01/21/2025

Was this solved? Is this context only a cancellation flag or does it do something more? The obvious solution for a cancellation trigger would be to have cancellation as an optional second argument. That's how it's solved in e.g. C#. Failing to pass the argument just makes it CancellationToken.None, which is simply never cancelled. So I/O without cancellation is simply foo.ReadAsync(x) and with cancellation it's foo.ReadAsync(x, ct).

show 1 reply
the_duke01/21/2025

Needs a (2017)!

show 1 reply
miffy90001/21/2025

> First things first, let’s establish some ground. Go is a good language for writing servers, but Go is not a language for writing servers. Go is a general purpose programming language, just like C, C++, Java or Python

Really? Even years later in 2025, this never ended up being true. Unless your definition of 'general purpose' specifically excludes anything UI-related, like on desktop, web or mobile, or AI-related.

I know it's written in 2017, but reading it now in 2025 and seeing the author comparing it to Python of all languages in the context of it's supposed 'general purpose'ness is just laughable. Even Flutter doesn't support go. granted, that seems like a very deliberate decision to justify Dart's existence.

show 3 replies
disintegrator01/22/2025

Consider what happens in JavaScript when you declare a function as async. Now everything calling it is infected. Passing around runtime constructs like context in Go (AbortSignal in JS) or an allocator in Zig gives exactly the right level control back to the call and I love it. You can bail out of context propagation at any level of your program if that's your desire.

mrkeen01/21/2025

> If the Go language ever comes to the point where I’d have to write this

  n, err := r.Read(context.TODO(), p)
> put a bullet in my head, please.

Manually passing around a context everywhere sounds about as palatable as manually checking every return for error.

show 1 reply
dang01/21/2025

Discussed at the time:

Context should go away for Go 2 - https://news.ycombinator.com/item?id=14951753 - Aug 2017 (40 comments)

skywhopper01/21/2025

I agree so strongly with this piece. Go’s context lib is essential, confusing, functional, and should be handled at the language level, but like this author I also have no ideas for what the design should be.

n144q01/21/2025

I find "CancellationToken" in VSCode extension APIs quite clear and usable, and not overly complicated. Wonder if anyone has done a conparison of Go's context and CancellationToken.

show 1 reply
sir_eliah01/21/2025

> If you use ctx.Value in my (non-existent) company, you’re fired

What a nice attitude.

theThree01/21/2025

Context is useful in many cases. In go I have to pass ctx from func to func. In nodejs I can easily create&use context by using AsyncLocalStorage (benefit of single-thread).

mickael-kerjean01/21/2025

> If you use ctx.Value in my (non-existent) company, you’re fired

I was unsuccessful to convey the same message in my previous company (apart from being fired part). All around the codebase you'd see function with official argument and unofficial ones via ctx that would panic everything if you forgot it was used 3 layers down (not kidding). The only use case I've seen so far that is not terrible of context value is if you have a layer of opentelemetry as it makes things transparent and as a caller you don't have to give a damn how the telemetry is operated under the hood.

pluto_modadic01/21/2025

new solution should be: Simple and elegant. Optional, non-intrusive and non-infectious. Robust and efficient. Only solves the cancelation problem.

okay... so they dodged the thing I thought was going to be interesting, how would you solve passing state? e.g. if I write a middleware for net/http, I have to duplicate the entire http.Request, and add my value to it.

rw_panic0_001/21/2025

there's no Go 2

show 1 reply
steve_adams_8601/21/2025

> If you use ctx.Value in my (non-existent) company, you’re fired

Yeah, okay. I tried to find reasons you'd want to use this feature and ultimately found that I really, really dislike it.

jbub01/21/2025

2017!!!