]> git.ralfj.de Git - web.git/blobdiff - personal/_posts/2025-07-24-memory-safety.md
java-safe wording
[web.git] / personal / _posts / 2025-07-24-memory-safety.md
index 6c4c7f6298a2358139d8eb5fc5a1512d7979aeb8..26a2427754f44102766a41798208992c6c62424b 100644 (file)
@@ -89,7 +89,7 @@ They even developed the [first industrially deployed concurrency memory model](h
 The result of all of this work is that in a concurrent Java program, you might see unexpected outdated values for certain variables, such as a null pointer where you expected the reference to be properly initialized, but you will *never* be able to actually break the language and dereference an invalid dangling pointer and segfault at address `0x2a`.
 In that sense, all Java programs are thread-safe.[^java-safe]
 
-[^java-safe]: Java programmers will sometimes use the terms "thread safe" and "memory safe" differently than C++ or Rust programmers would. From a Rust perspective, Java programs are memory- and thread-safe by construction. Java programmers take that so much for granted that they use the same term to refer to stronger properties, such as absence of null pointer exception or absence of unintended data races. However, Java null pointer exceptions and data races cannot cause segfaults from invalid pointer uses, so these kinds of issues are qualitatively very different from the memory safety violation in my Go example. For the purpose of this blog post, I am using the low-level Rust and C++ meaning of these terms.
+[^java-safe]: Java programmers will sometimes use the terms "thread safe" and "memory safe" differently than C++ or Rust programmers would. From a Rust perspective, Java programs are memory- and thread-safe by construction. Java programmers take that so much for granted that they use the same term to refer to stronger properties, such as not having "unintended" data races or not having null pointer exceptions. However, such bugs cannot cause segfaults from invalid pointer uses, so these kinds of issues are qualitatively very different from the memory safety violation in my Go example. For the purpose of this blog post, I am using the low-level Rust and C++ meaning of these terms.
 
 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.
@@ -99,7 +99,8 @@ 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.
 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.