This means it is, strictly speaking, not a memory safe language: the best the language can promise is that *if* a program has no data races (or more specifically, no data races on problematic values such as interfaces, slices, and maps), then its memory accesses will never go wrong.
Now, to be fair, Go comes with out-of-the-box tooling to detect data races, which quickly finds the issue in my example.
However, in a real program, that means you have to hope that your test suite covers all the situations your program might encounter in practice, which is *exactly* the sort of issue that a strong type system and static safety guarantees are intended to avoid.
-It is therefore not surprising that [data races are a huge problem in Go](https://dl.acm.org/doi/pdf/10.1145/3519939.3523720).
+It is therefore not surprising that [data races are a huge problem in Go](https://arxiv.org/pdf/2204.00764),
+and there is at least [anecdotal evidence of actual memory safety violations](https://www.reddit.com/r/rust/comments/wbejky/comment/ii9piqe).
I could accept Go's choice as an engineering trade-off, aimed at keeping the language simpler.
However, putting Go into the same bucket as languages that actually *did* go through the effort of solving the problem with data races misrepresents the safety promises of the language.
Even experienced Go programmers do not always realize that you can break memory safety without using any unsafe operations or exploiting any compiler or language bugs.
+Go is a language *designed* for concurrent programming, so people do not expect footguns of this sort.
I think that is a problematic blind spot.
The [Go memory model documentation](https://go.dev/ref/mem) is not exactly upfront about this point either: the "Informal Overview" emphasizes that "most races have a limited number of outcomes" and remarks that Go is unlike "C and C++, where the meaning of any program with a race is entirely undefined".
In practice, of course, safety is not binary, it is a spectrum, and on that spectrum Go is much closer to a typical safe language than to C.
It is plausible that UB caused by data races is less useful for attackers than UB caused by direct out-of-bounds or use-after-free accesses.
But at the same time I think it is important to understand which safety guarantees a language reliably provides, and where the fuzzy area of trade-offs begins.
+I am in the business of [*proving*](https://research.ralfj.de/thesis.html) safety claims of languages, and for Go, there's not much one could actually prove.
I hope this post helps you to better understand some the non-trivial consequences of the choices different languages have made.[^go] :)
[^go]: In case you are wondering why I am focusing on Go so much here... well, I simply do not know of any other language that claims to be memory safe, but where memory safety can be violated with data races. I originally wanted to write this blog post years ago, when Swift was pretty much in the same camp as Go in this regard, but Swift has meanwhile introduced "strict concurrency" and joined Rust in the small club of languages that use fancy type system techniques to deal with concurrency issues. That's awesome! Unfortunately for Go, that means it is the only language left that I can use to make my point here. This post is not meant to bash Go, but it is meant to put a little-known weakness of the language into the spotlight, because I think it is an instructive weakness.