13 ms·
Context should go away for Go 2 (2017)
- steve_adams_86 2y ago> 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.
- mrkeen 2y ago> 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.
- the_gipsy 2y agoExactly, the snippets needs at least three lines of inane error checking boilerplate and variable juggling.
- the_duke 2y agoNeeds a (2017)!
- pansa2 2y agoYes, I was about to comment that “there won’t be a Go 2”, but I guess that wasn’t settled when the article was written.
- riffraff 2y agoas someone who's not in the community: why not?
- orian 2y agoTo not repeat other's (Python) mistakes ;-)
- phire 2y agoThe the introduction of Python 3 wasn't a mistake. The mistake was discontinuing Python 2. Just look at how rust does it. Rust 1.0 code still works in the latest version of rustc, you just need to set the project to the Rust 2015 edition. You can even mix-and-match editions, as each crate can have a different edition. Newer versions of rustc will always support all previous editions, and breaking changes are only ever introduced when a new edition is released every 3 years. If the crate is stable, no real reason to upgrade, it will work forever. And if you do need to update a project, you can split it into multiple crates and do it incrementally. Just imagine how much smoother the python 3 transition would have been if you could transition projects incrementally, module by module as needed.
- 9rx 2y agoIt seems you are both saying the same thing. Had Python not introduced a line in the sand and instead continued to support Python 2 amid future updates there would have been no reason for Python 3. The Python 2 line could have kept improving instead. Just as you say, Python could have introduced what is found in Python 3 without breaking Python 2 support. Which is the direction Go has settled on; hence why Go 2 is off the table. Go 1.0 and Go 1.23 are very different languages, but backwards version support is retained, so no need for a new major version.
- phire 2y agoNo. The point of rust editions is that they do break support for older code, which is very different to what go has now settled on. IMO, it's the best of both worlds. Old code continues to work forever, but your language design isn't held back by older design mistakes.
- 2y ago
- the_gipsy 2y ago> 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.
- grose 2y agoThe blog post from 2014 introducing context uses a private key type, so there's really no excuse: https://go.dev/blog/context#package-userip https://go.dev/blog/context#package-userip
- pluto_modadic 2y agonew 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.
- mukunda_johnson 2y ago> 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.
- rednafi 2y agoGoroutines have a tiny stack at the beginning, 4KB iirc. Having a goroutine-local storage will probably open a can of worms there.
- PaulKeeble 2y agoContext's spread just like exceptions do, the moment you introduce one it flies up and down all the functions to get where it needs to be. I can't help but think that local storage and operations for Go just like Threads have in Java would be a cleaner solution to the problem.
- arccy 2y agonow your stuff breaks when you pass messages between channels
- kflgkans 2y agoI like explicit over implicit. I will take passing down context (in the sense of the concept, not the specific Go implementation) explicitly everywhere over implicit ("put it somewhere and I'll trust I can [probably, hopefully] get it back later") any day of the week. I've seen plenty of issues in Java codebases where there was an assumption some item was in the Thread Local storage (e.g. to add some context to a log statement or metric) and it just wasn't there (mostly because code switched to a different thread, sometimes due to a "refactor" where stuff was renamed in one place but not in another).
- pm90 2y agoMost recently ive been bit by this with datadog. The Python version does some monkeypatching to inject trace info. The go version you need to inject the trace info explicitly. While the latter takes more setup, it was much easier to understand what was going on and to debug when we ran into issues.
- rednafi 2y agoThis 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.
- incognito124 2y agoI thought go 2 was considered harmful
- rednafi 2y agoOh, don't even start about Go's knack for being pithy to a fault.
- TeMPOraL 2y agoYes, that's why you should instead use "COMEFROM", or it's more general form, "LET'S HAVE A WALK".
- robertlagrant 2y agoI came here to say this.
- bheadmaster 2y agoContexts 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.
- bryancoxwell 2y agoThis works, but goes against convention in that (from the context package docs) you shouldn’t “store Contexts inside a struct type; instead, pass a Context explicitly to each function that needs it.”
- captainmuon 2y agoThis 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).
- crowcountry 2y agoScala has implicit contextual parameters: https://docs.scala-lang.org/tour/implicit-parameters.html https://docs.scala-lang.org/tour/implicit-parameters.html.
- agumonkey 2y agoI've always been curious about how this feature ends up in day to day operations and long term projects. You're happy with it ?
- kloop 2y agoAs a veteran of a large scala project (which was re-written in go, so I'm not unbiased), no. I was generally not happy. This was scala 2, so implicit resolution lookup was a big chunk of the problem. There's nothing at the call site that tells you what is happening. But even when it wasn't hidden in a companion object somewhere, it was still difficult because every import change had to be scrutinized as it could cause large changes in behavior (this caused a non-zero number of production issues). They work well for anything you would use environment variables for, but a chunk of the ecosystem likes to use them for handlers (the signature being a Functor generally), which was painful
- jbub 2y ago2017!!!
- sir_eliah 2y ago> If you use ctx.Value in my (non-existent) company, you’re fired What a nice attitude.
- mickael-kerjean 2y ago> 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.
- rw_panic0_0 2y agothere's no Go 2
- euroderf 2y ago"Go 2 Considered Harmful"
- alkonaut 2y agoWas 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).
- whstl 2y agoIt's not just for cancellation and timeouts, it is also used for passing down metadata, but also for cross-cutting concerns like structured loggers.
- deleted 2y ago[deleted]
- kalekold 2y ago> 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)
- b1-88er 2y agoMaybe he doesn't have a company because he is too dogmatic about things that don't really matter.
- PUSH_AX 2y ago100% People who have takes like this have likely never zoomed out enough to understand how their software delivery ultimately affects the business. And if you haven't stopped to think about that you might have a bad time when it's your business.
- daviddever23box 2y agoBingo. Everything that can be wrongly used or abused started out its existence within sane constraints and use patterns.
- pm90 2y agoSomeone has to question the status quo. If we just did the same things there would be a lot less progress. The author took the time to articulate their argument, and publish it. I appreciate their effort even if I may not agree with their argument.
- frankie_t 2y agoThe author gave a pretty good reasoning why is it a bad idea, in the same section. However, for the demonstration purposes I think the they should have included their vision on how the request scoped data should be passed. As I understand they propose to pass the data explicitly, like a struct with fields for all possible request-scoped data. I personally don't like context for value passing either, as it is easy to abuse in a way that it becomes part of the API: the callee is expecting something from the caller but there is no static check that makes sure it happens. Something like passing an argument in a dictionary instead of using parameters. However, for "optional" data whose presence is not required for the behavior of the call, it should be fine. That sort of discipline has to be enforced on the human level, unfortunately.
- deleted 2y ago[deleted]
- miffy900 2y ago> 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.
- pjmlp 2y agoIn an alternative timeline, had Rust 1.0 been available when Docker pivoted away from Python into Go, and Kubernetes from Java into Go, due to having Go folks pushing for the rewrite, and most likely they would have been taken by RIIR instead, nowadays spreading across Python and JavaScript ecosystem, including rewriting tools originally written in Go.
- dlisboa 2y ago> Unless your definition of 'general purpose' specifically excludes anything UI-related, like on desktop, web or mobile, or AI-related. By that definition no language is general purpose. There is no language today that excels in GUI (desktop/mobile), web development, AI, cloud infrastructure, and all the other stuff like systems, embedded...And all at the same time. For instance I have never seen or heard of a successful Python desktop app (or mobile for that matter).
- theThree 2y agoContext 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).
- nickcw 2y agoContexts 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.
- n144q 2y agoI 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.
- smashedtoatoms 2y agoYeah, .NET developers have been passing CancellationTokens around in the places where they have needed them for 15 years. The tokens are basically invisible until their existence emerges when someone decides they want to cancel a long-running API call or something. At that point, they are plumbed as deeply as seems fit for the problem at hand and then hardly thought about ever again. CancellationTokens are generally a delightful pattern, especially when the language allows sensible defaults.
- skywhopper 2y agoI 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.
- dang 2y agoDiscussed at the time: Context should go away for Go 2 - https://news.ycombinator.com/item?id=14951753 https://news.ycombinator.com/item?id=14951753 - Aug 2017 (40 comments)
- disintegrator 2y agoConsider 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.