There is no memory safety without thread safety

Go’s data-race issue in practice

  • Several commenters recount hard-to-debug Go races, including one where a loop counter overflow turned some requests from ~100ms into minutes; race detectors didn’t catch it.
  • Others say they’ve almost never seen this specific “torn read of fat pointers/slices” in production despite large Go deployments.
  • Uber’s study (linked in thread) found 2,000 races across ~50M LoC / ~2,100 services (1 race per 25k LoC), but most did not cause serious outages.
  • Consensus: the bug is real and nasty when it happens, but empirically infrequent; many teams rely on patterns, tooling, and paranoia rather than language guarantees.

What “memory safety” should mean

  • One camp: “memory safety” is binary and means “no UB-style memory corruption in any program written without explicit unsafe,” including in concurrent code. Under that definition Go is not memory safe.
  • Another camp (security-oriented): “memory safety” is a term of art meaning “no practically exploitable memory corruption vulnerabilities”; by that standard Go qualifies until someone shows a convincing RCE in real-world Go.
  • Long subthread on UB vs unspecified behavior; some argue Go’s data-race-induced type confusion clearly violates typical academic/PL definitions of memory safety, even if exploitation is hard.
  • Java, C#, OCaml, Swift ≥6, Rust, etc. are cited as languages whose models ensure races cannot produce invalid pointers or out-of-bounds memory, distinguishing them from Go.

Rust, Go and productivity tradeoffs

  • Go is praised for “easy concurrency” (goroutines, channels) and high productivity for web services and glue code; many SaaS backends accept some correctness risk for speed of delivery.
  • Rust is viewed as excellent for traditional shared-memory concurrency and eliminating data races, but with higher cognitive load (lifetimes, ownership) and rougher async story.
  • Some practitioners claim Rust’s up‑front cost is repaid by far fewer production incidents and lower total cost of ownership; others insist Go (and Python, C# etc.) keep them faster overall, especially once experienced.
  • There’s disagreement over whether Rust’s productivity now matches Go’s in practice; internal Google data is mentioned claiming similar team productivity.

Concurrency models and channels

  • Despite “share memory by communicating,” real Go programs frequently share memory directly. Channels don’t prevent races without a notion of ownership or sendable types.
  • Example given where sending bytes.Buffer.Bytes() over a channel races with Reset() and mutates data being processed; reviewers/tools might catch it, but the language can’t.
  • Some argue channels + discipline are “concurrency 101”; others reply that subtle, indirect sharing in large codebases still routinely slips through reviews.

Security and exploitability debate

  • A published Go data-race based exploit shows type confusion leading to arbitrary code execution, but relies on highly contrived patterns.
  • Security-focused commenters demand an exploit against a non‑toy, real Go service before reclassifying Go as “memory-unsafe” in the security sense.
  • Others counter that once UB exists, conservative engineering must assume worst-case (potential RCE), even if no public exploit exists yet.

Language ecosystems and evolution

  • Swift is in the middle of a painful transition to data‑race freedom; its new concurrency model is seen as safer but complex and tooling-heavy.
  • Zig’s and Pony’s claims about safety are questioned; without a Rust‑like ownership model, concurrency may still be unsound.
  • There’s meta‑discussion that tool and language “politics” skew these debates; many note most real-world bugs stem from higher‑level logic or database concurrency, not just language memory models.