X-Git-Url: https://git.ralfj.de/web.git/blobdiff_plain/16cc3f082af9dfab71ad81f08c410a9a3d1167b9..f2e0cb781b6f9b1de1abd6da079cd2022cc9104f:/personal/_posts/2025-07-24-memory-safety.md?ds=inline diff --git a/personal/_posts/2025-07-24-memory-safety.md b/personal/_posts/2025-07-24-memory-safety.md index b2215d9..c12a125 100644 --- a/personal/_posts/2025-07-24-memory-safety.md +++ b/personal/_posts/2025-07-24-memory-safety.md @@ -93,7 +93,7 @@ In that sense, all Java programs are thread-safe.[^java-safe] Generally, there are two options a language can pursue to ensure that concurrency does not break basic invariants: - Ensure that arbitrary concurrent programs actually behave "reasonably" in some sense. This comes at a significant cost, restricting the language to never assume consistency of multi-word values and limiting which optimizations the compiler can perform. This is the route most languages take, from Java to C#, OCaml, JavaScript, and WebAssembly. -- Have a strong enough type system to fully rule out data races on most accesses, and pay the cost of having to safely deal with races for only a small subset of memory accesses. This is the approach that Rust first brought into practice, and that Swift is now also [adopting](https://developer.apple.com/documentation/swift/adoptingswift6). +- Have a strong enough type system to fully rule out data races on most accesses, and pay the cost of having to safely deal with races for only a small subset of memory accesses. This is the approach that Rust first brought into practice, and that Swift is now also adopting with their ["strict concurrency"](https://developer.apple.com/documentation/swift/adoptingswift6). Go, unfortunately, chose to do neither of these. 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. @@ -104,6 +104,7 @@ It is therefore not surprising that [data races are a huge problem in Go](https: 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". @@ -128,4 +129,4 @@ It is plausible that UB caused by data races is less useful for attackers than U 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 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 made great strides on the concurrency side 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. +[^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.