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.Client example 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 go keyword 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.