A million ways to die from a data race in Go
Value and validity of the article
- Several commenters find the examples realistic, matching issues they’ve debugged in real Go code and other writeups (e.g. Uber’s race patterns).
- Others argue some examples are “beginner-level” or even wrong (e.g. per‑request mutex protecting shared data, odd “fixes”), casting doubt on the claimed experience.
- There’s disagreement whether this is “crapping on Go” or necessary documentation of real pitfalls, especially for newcomers.
Go’s concurrency model and data races
- Go’s slogan “don’t communicate by sharing memory…” is seen as aspirational: goroutines all share heap memory, so it’s easy to accidentally share mutable state.
- Many note that Go does not enforce message passing; you must voluntarily avoid shared mutability using channels and good patterns.
- Others counter that threads/goroutines by definition share memory; coordination via mutexes, atomics, queues is normal and expected in a low‑level language.
Language design, tooling, and footguns
- The
:=shadowing/closure example is broadly acknowledged as a real footgun; Go offers no language-level protection and relies on human care and IDE highlighting. - Critics argue this is precisely what modern language design should prevent from compiling; proponents say “you must understand the memory model and docs”.
- Some praise Go tooling (race detector, IDE support); others say it’s nowhere near Java’s or even below average, with weaker debugging and heavier language server.
Comparisons to other ecosystems
- Rust: similar code would be compile‑time errors; the borrow checker primarily prevents data races, not just memory leaks. Unsafe code can still race, but is localized.
- JVM / .NET: data races can cause logical bugs but not corrupt the runtime; this is contrasted with Go’s potential for memory issues via races on “fat pointers” like slices.
- Java/Kotlin: immutable HTTP clients and structured concurrency reduce entire classes of bugs.
- Haskell, Erlang/Elixir, Rust are cited as languages that largely prevent these races by design.
APIs, mutability, and http.Client
- The
http.Clientexample splits opinions: some say “concurrent use vs modification” is clear and consistently documented; others find such linguistic distinctions too subtle for concurrency safety. - Several wish Go had explicit immutability (immutable structs/fields, builder patterns) or clearer “Sync*” types to make safe sharing obvious.
Erlang/Elixir and alternative models
- Elixir/BEAM are described as eliminating data races via immutability and isolated processes; you still get logical races, deadlocks, leaks, and resource exhaustion, but not memory‑model violations.
- Compared to Go, Elixir is viewed as far better suited for highly concurrent network services, at the cost of being less general‑purpose.
Power vs safety vs productivity
- Some argue any powerful language must allow you to “shoot yourself in the foot”; otherwise it’s dismissed as a toy.
- Others respond that Go’s combination of non‑thread‑safe defaults with a trivial
gokeyword is an unsafe default, especially for large teams. - There’s a recurring sentiment that Go makes you feel productive (fast compiles, simple syntax), while hiding substantial concurrency hazards.