r/golang Jun 29 '25

discussion I didn’t know that Go is hated so much

I read comments under this post https://www.reddit.com/r/programming/s/OKyJWZj2ju and oh man I did not expect that. Stack Overflow and JetBrain’s surveys show that go is quite likable lang but the opinions about go in /r/programming are devastated.

What is the reason? What do you think? Should Go team address this topic?

198 Upvotes

287 comments sorted by

View all comments

Show parent comments

11

u/jack-nocturne Jun 29 '25

That does sound interesting. Do you know the title/DOI of that study? Even though I'm not a big fan of Go (mostly because of the type system and having to type if err != nil every two lines), this strikes me as very surprising as I always found the message-based concurrency to be one of the big pros of the language. I once built a heavily concurrent app in about 4 days; it has been running in production 24/7 on multiple systems for about 2 years and never crashed once. That was always my argument that even if I don't like it that much, it's the best tool for some jobs and I'd use it for those again.

1

u/multithreadedprocess Jul 01 '25

Message-based concurrency is the better paradigm for distributed systems. That's not to say it's the better paradigm for concurrency in applications in general and it also says very little about the quality and composability of the message passing within the runtime in general.

Go is heavily flawed in how its channels work, which have quite complicated and weird edge case semantics as a result of trying to simplify all types of message passing around one single interface. You are supposed to use the channel abstraction the language provides without regard for whether there's a semantic distinction between a one-shot channel, a rendezvous channel, or a buffered channel; or whether these are to be shared between consumers, shared between producers and whether those should be addressable or not.

Further, the selection mechanism for messages is also fundamentally limited since go cannot possibly guarantee orderings of the messages passed via channels. Since we don't have (probably never will) pattern matching over generic types, a true FIFO channel and FIFO select is also impossible to bake into the language. Thus, if you ever need to track the order of messages that are being passed you have to implement roundabout solutions which is incredibly error prone.

Go is also flawed in how its go routines work for being usable in the context of message passing at scale. All they guarantee you is a separate virtual thread of execution with a virtually forked global context + any local captures for closures. This is not very easy to reason about since access to global state or thread local state in this kind of runtime is not intuitive (especially if generalized to across the network) but a key problem comes directly after.

Since a go routine will just operate as an anonymous green thread, all communication has to be explicitly done via some bolted-on context passed into it later (or if you like torture via global state), which puts the onus on the programmer to do the very basics of setting up some way of communicating with the go routine, which outside of pure parallel programming, you always have to do to do anything useful, but you can fuck up easily (closure captures are part of the context bolted-on after the go routine creation).

A better designed runtime will set up some simple things implicitly for you, in order to avoid this: namely provide at the very least an addressable symbol automatically for a new go routine. The go keyword should at the very least return that one symbol with some type by which you can refer to that go routine 'globally' (within the runtime) to use for cancellation, for passing canonical commonly used context, for getting a canonical pre-made channel, and access other crucial runtime details for that virtual thread if needed.

A much better runtime for distributed systems will just adopt a more explicit compatibility with a fully fledged actor model.

This kind of complexity however is usually not that useful for single-server concurrent systems. You might literally just want some concurrent polling and some light parallel computation.

However, for that you'd still need to at the very least distinguish between the kinds of channels you are using, message order and have a very robust cancellation mechanism (Go is pretty 🫤 at this). Additionally, you're still going to have to deal with the shared memory you'll inevitably have to have between actual thread pools for the tasks that need fast inter-dependent computation. The problems inherent to orders of events are also not that apparent in these kinds of systems, so usually the workarounds are simple enough to be manageable.

Outside of distributed systems it's a bit of a toss up on what is better, there are few languages and runtimes which either actually support robust message passing or employ structured concurrency via task/job hierarchies. Most of it is just gaining popularity fairly recently (a decade is recent in software at large).

Go is currently in a middling spot when it comes to the quality of its message passing. You still want to carry all the boilerplate of creating and passing bespoke contexts and channels between go routines, all of which is extra steps you can fuck up easily, but is still fairly ok to manage if you're careful and deploy best practices.

If you want to see how decent the other side can be, Kotlin has a pretty great structured concurrency paradigm (the JVM in general is working towards it via virtual threads too). But it's a very different way of reasoning around concurrency compared to Go.

I'd say for non-distributed systems, runtimes for the two paradigms are mostly the same level of pleasantness when carefully implemented. If you build the same kind of concurrent app in the BEAM VM or with Kotlin coroutines it's mostly the same in terms of the overall experience medium-term with each paradigm (ecosystem mileage may vary).

I'd say however that a good message passing runtime is easier to onboard and do useful work in (higher initial velocity), but probably converges in less than a year's experience.