A million ways to die from a data race in Go
11 comments
·November 22, 2025bilbo-b-baggins
landr0id
First sentence:
>I have been writing production applications in Go for a few years now. I like some aspects of Go. One aspect I do not like is how easy it is to create data races in Go.
Their examples don't seem terribly convoluted to me. In fact, Uber's blog post is quite similar: https://www.uber.com/blog/data-race-patterns-in-go/
speedgoose
I assume you are aware of "the billion dollar mistake" from Tony Hoare?
iambvk
Only looked at the first two examples. No language can save you when one writes bad code like that.
unscaled
You can argue about how likely is code like that is, but both of these examples would result in a hard compiler error in Rust.
A lot of developers without much (or any) Rust experience get the impression that the Rust Borrow checker is there to prevent memory leaks without requiring garbage collection, but that's only 10% of what it does. Most the actual pain dealing with borrow checker errors comes from it's other job: preventing data races.
And it's not only Rust. The first two examples are far less likely even in modern Java or Kotlin for instance. Modern Java HTTP clients (including the standard library one) are immutable, so you cannot run into the (admittedly obvious) issue you see in the second example. And the error-prone workgroup (where a single typo can get you caught in a data race) is highly unlikely if you're using structured concurrency instead.
These languages are obviously not safe against data races like Rust is, but my main gripe about Go is that it's often touted as THE language that "Gets concurrency right", while parts of its concurrency story (essentially things related to synchronization, structured concurrency and data races) are well behind other languages. It has some amazing features (like a highly optimized preemptive scheduler), but it's not the perfect language for concurrent applications it claims to be.
fud101
Not all languages look that ugly without trying either.
fud101
What a welcome antidote to the Go lang hype.
__loam
In the first one, he complains that one character is enough to cause an issue, but the user should really have a good understanding of variable scope and the difference between assignment and instsntiation if they're writing concurrent go code. Some ides warn the user when they do this with a different color.
Races with mutexes can indicate the author either doesn't understand or refuses to engage with Go's message based concurrency model. You can use mutexes but I believe a lot of these races can be properly avoided using some of the techniques discussed in the go programming language book.
TheDong
The mutex case is one where they're using a mutex to guard read/writes to a map.
Please show us how to write that cleanly with channels, since clearly you understand channels better than the author.
I think the golang stdlib authors could use some help too, since they prefer mutexes for basically everything (look at sync.Map, it doesn't spin off a goroutine to handle read/write requests on channels, it uses a mutex).
In fact, almost every major go project seems to end up tending towards mutexes because channels are both incredibly slow, and worse for modeling some types of problems.
... I'll also point out that channels don't save you from data-races necessarily. In rust, passing a value over a channel moves ownership, so the writer can no longer access it. In go, it's incredibly easy to write data-races still, like for example the following is likely to be a data-race:
handleItemChannel <- item
slog.Debug("wrote item", "item", item) // <-- probably races because 'item' ownership should have been passed along.konart
>he complains that one character is enough
He complains that language design offers no way of avoiding it (in this particular case) and relies only on human or ide. Humans are not perfect and should not be a requirement to write good code.
xlii
Whatever the case Go's tooling (i.e. IDE part) is one of the best in class and I think it shouldn't be be dismissed in the context of some footguns that Go has.
4 ways to demonstrate that the author either knows nothing about closures, structs, mutexes, and atomicity OR they just come from a Rust background and made some super convoluted examples to crap on Go.
“A million ways to segfault in C” and its just the author assigning NULL to a pointer and reading it, then proclaiming C would be better if it didn’t have a NULL value like Rust.
I’m mad I read that. I want a refund on my time.