Live data from Hacker News

There is no memory safety without thread safety

ralfj.de

481–490 of 517 posts

Re: There is no memory safety without thread safety

#481
post #397
post #255

Earlier quoted context omitted.

The definition kind of changed. At the time Go was created, it met one common definition of "memory safety", which was essentially "have a garbage collector". And compared to c/c++, it is much safer.

> it met one common definition of "memory safety", which was essentially "have a garbage collector" This is the first time I hear that being suggested as ever having been the definition of memory safety. Do you have a source for this? Given that except for Go every single language gets this right (to my knowledge), I am kind of doubtful that this is a consequence of the term changing its meaning.

True, "have a garbage collector" was never the formal definition, it was more "automatic memory management". But this predates the work on Rust's ownership system and while there were theories of static automatic memory management, all practical examples of automatic memory management were some form of garbage collection.

If you go to the original 2009 announcement presentation for Go [1], not only is "memory-safety" listed as a primary goal, but Pike provides the definition of memory-safe that they are using, which is:

"The program should not be able to derive a bad address and just use it"

Which Go mostly achieves with a combination of garbage collection and not allowing pointer arithmetic.

The source of Go's failure is concurrency, which has a knock-on effect that invalidates memory safety. Note that stated goal from 2009 is "good support for concurrency", not "concurrent-safe".

[1] https://youtu.be/rKnDgT73v8s?t=463

Re: There is no memory safety without thread safety

#482

Earlier quoted context omitted.

> Kubernetes services are one of the places where you don't care about startup time. There are some kubernetes services that scale up and down. And even for those that don't normally, if they have some kind of failure, the difference between taking a millisecond to get back up and taking a second can actually matter for a web host. > Go is not particularly fast. People often see that Java gets faster as it runs and t…

I wouldn't call the Go GC mediocre, it's one of the few fully concurrent GC's in common use. It probably has significantly lower memory demand than Java/NET for comparable workloads.

Go's GC is not compacting, it has significant mutator overhead on every pointer write, it has an expensive allocator (it has to search through free lists, since it can't compact), and the free pahse has to do work proportional to the amount of garbage the program produces. It also produces no debugging information for ownership, for some bizarre reason (you can't tell from a Go heap dump with any standard Go tool which object is keeping another object around).

The fact that it can do this mostly concurrently (it's not fully concurrent, actually) is not that special - Java's latest GC is also mostly concurrent, without having any of the other previous drawbacks.

Go's big advantage in terms of memory management is that it doesn't produce as much garbage as most GC languages. The fact that it natively supports value types, and actually uses them extensively in the standard library (unlike C#, which also supports these), and it's excellent stack escape analysis which allows many allocations to stay on the stack and outside the GC heap entirely, is what makes Go have such little memory overhead.

Re: There is no memory safety without thread safety

#483

Earlier quoted context omitted.

> But: everybody understands that. I had to convince Go people that you can segfault with Go. Or you mean the language designers with using everybody?

You can segfault in Rust, too - there's a whole subset of the language marked "unsafe" that people ignore when making "safe language" arguments. The question is how difficult is it to have a segfault, and in Go it's honestly pretty hard. It's arguably harder in Rust but it's not impossible.

It's impossible in safe Rust (modulo compiler bugs and things like using a debugger to poke in the program's memory from the outside). That's the key difference.

Of course unsafe Rust is not memory safe. That's why it is called like that. :) Go has unsafe operations too (https://go.dev/ref/spec#Package_unsafe), and of course if you use those all bets are off. But as you will notice, my example doesn't use those operations.

Re: There is no memory safety without thread safety

#484
post #30

Earlier quoted context omitted.

I think the true answer is that the moment you have to do tricky concurrency in Go, it becomes less desirable. I think that Go is still better at tricky concurrency than C, though there are some downsides too (I think it's a bit easier to sneak in a torn read issue in Go due to the presence of fat pointers and slice headers everywhere.) Go is really good at easy concurrency tasks, like things that have almost no shar…

> But I struggle to be as productive You should calculate TCO in productivity. Can you write Python/Go etc. faster? Sure! Can you operate these in production with the same TCO as Rust? Absolutely not. Most of the time the person debugging production issues and data races is different than the one who wrote the code. This gives the illusion of productivity being better with Python/Go. After spending 20+ years around p…

TCO? Tail call optimization? But that doesn't make sense in this context. hm.

Re: There is no memory safety without thread safety

#485
post #481
post #397

Earlier quoted context omitted.

> it met one common definition of "memory safety", which was essentially "have a garbage collector" This is the first time I hear that being suggested as ever having been the definition of memory safety. Do you have a source for this? Given that except for Go every single language gets this right (to my knowledge), I am kind of doubtful that this is a consequence of the term changing its meaning.

True, "have a garbage collector" was never the formal definition, it was more "automatic memory management". But this predates the work on Rust's ownership system and while there were theories of static automatic memory management, all practical examples of automatic memory management were some form of garbage collection. If you go to the original 2009 announcement presentation for Go [1], not only is "memory-safety"…

Thanks! I added a reference to that in the blog post.

Interestingly, in 2012 Rob Pike explicitly said that Go is "not purely memory safe" because "sharing is legal": https://go.dev/talks/2012/splash.slide#49. However it is not entirely clear what he means by that (I was not able to find a recording of the talk), but it seems likely he's referring to this very issue.

> "The program should not be able to derive a bad address and just use it"

My example does exactly that, so -- as you say, Go mostly achieves this, but not entirely.

> Note that stated goal from 2009 is "good support for concurrency", not "concurrent-safe".

My argument is that being concurrency-unsafe implies being memory-unsafe, for the reasons laid down in the blog post. I understand that that is a somewhat controversial opinion. :)

Re: There is no memory safety without thread safety

#486
post #125

Earlier quoted context omitted.

No, it's how one very specific community of experts understands it. It is not some kind of universal law of definition that it must mean that always and everywhere. As far as what is confusing, that is a matter of perspective. I think it is confusing (to put it mildly) that the C community has chosen to use "undefined behavior" to mean "it must never happen, and anything goes if it does". That is extremely counterint…

It is also not what the C community has chosen. It is what was imposed on us by certain optimizing compilers that used the interpretation that gave them maximum freedom to excel in benchmarks, and it was then endorsed by C++. The C definition is that "undefined behavior" can have arbitrary concrete behavior, not that a compiler can assume it does not happen. (that form semantic people prefer the former because it mak…

> The C definition is that "undefined behavior" can have arbitrary concrete behavior, not that a compiler can assume it does not happen.

What is the difference between those? How does a compiler that assumes UB never happens violate the requirement that UB can have arbitrary concrete behavior? If we look at a simple example like optimizing "x + y > x" (signed arithmetic, y known to be positive) to "true" -- that will lead to some arbitrary concrete behavior of the program, so it seems covered by the definition.

I assume that what the original C authors meant was closer to "on signed integer overflow, non-deterministically pick some result from the following set", but that's not what they wrote in the standard... if you want to specify that something is non-deterministic, you need to spell out exactly what the set of possible choices are. Maybe for singed integer overflow one could infer this (though it really should be made explicit IMO), but C also says that the program has UB "by default" if it runs into a case not described by the standard, and there's just no way to infer a set of choices from that as far as I can see.

Re: There is no memory safety without thread safety

#487
post #405

Earlier quoted context omitted.

Other than in the sense of SMT (Hyper-Threading)? I don't think so. Threads are a software concept. One can distinguish between native (OS) threads and green (language-runtime) threads which may use a different context-switching mechanism. But that's more of a spectrum in terms of thread-safety; similar to how running multiple threads on a single CPU core without SMT, single CPU core with SMT, multiple CPU cores, wit…

Runtime-switched tasks cannot lead to memory unsafety unless multiple OS threads are involved, because that's the only case where torn writes are possible. And a typical configuration of Go will not be running multiple OS threads unless multiple hardware threads (aka "logical cores", "virtual processors" etc.) are available.

Thread-safety isn’t just about word tearing. It’s also about state invariants in your program. Even if all your threads are green threads on the same OS thread, you still have to guard critical sections that check-and-modify, or that temporarily break the invariants of state that is observable by other threads, when there’s anything in-between that may lead to a context switch. Otherwise, while you may be free of word tearing, you might still have “torn” program state.

On a single-core CPU, word tearing may similarly be absent between OS threads, but you still have to guard any critical section.

I agree with user swiftcoder that there are concurrency issues (like the above) even in the absence of hardware parallelism, or of multiple OS threads (which by themselves don’t imply hardware parallelism). I disagree that “thread-safety” isn’t an appropriate term for them. Those issues are part of what thread-safety was always about.

Re: There is no memory safety without thread safety

#488
post #305

Earlier quoted context omitted.

Rust is objectively harder to write than Go (or any other GC-language) because it exposes more concerns for the programmer to take care of. With Rust, you must always ensure your program complies with the borrow checker's rules (which is what makes Rust memory-safe) which includes having to add lifetime annotations to your variables in many cases. Go just doesn't have any of that. You could argue Rust still has an ad…

> an experienced Go developer probably has internalized how to avoid those bugs and the cost of preventing them can be nearly negligible. And an experienced Rust developer has internalized the patterns (such as cloning or ARC) that are needed to cope with the borrow checker while writing prototype-quality, quick-iteration code. What's easier, fixing hard-to-spot bugs in the code or getting that code to compile in the…

I am not a fan of Golang or the approach taken to designing it, but I will say, that writing code in a certain way may even have zero cost, because after some time it may be natural to write code that way to someone. For example this works for programming paradigms. I am just as familiar with FP as with OOP and when writing FP code I avoid mutation. Does that make my code writing slower? Only in so far as a problem inherently is or is not more difficult to solve without mutation.

Re: There is no memory safety without thread safety

#489
post #385

Earlier quoted context omitted.

Java also sometimes uses "memory safe" to refer to programs that don't have null pointer exceptions. So in that sense, Java isn't memory safe by construction either. These terms are used slightly differently by different communities, which is why I discuss this point in the article. But you seem adamant that you have the sole authority for defining these terms so :shrug:

When those US government articles about how we should switch to memory safe languages come out, they refer to Java as a “memory safe language”. They also count data race freedom as part of memory safety, which I think is wrong (and contradicts their inclusion of Java and even Go in the list of memory safe languages). So no, I’m not an authority. I’m just following the general trend of how the term is used. And ive ne…

> They also count data race freedom as part of memory safety, which I think is wrong (and contradicts their inclusion of Java and even Go in the list of memory safe languages).

For Java, there's no contradiction if you define data race freedom as "data races cannot cause arbitrary memory corruption / UB".

> And ive never heard “memory safe” used in relation to not having null pointer exceptions. That’s a new one and sounds nonsensical, frankly

I was also surprised, but it's what I was told by people working on verification of Java programs. And you can see e.g. at https://link.springer.com/content/pdf/10.1007/978-3-030-1750... that people are proving memory safety of Java programs, which would not make sense at all if all Java programs are memory safe by construction.

Re: There is no memory safety without thread safety

#490
post #486
post #125

Earlier quoted context omitted.

It is also not what the C community has chosen. It is what was imposed on us by certain optimizing compilers that used the interpretation that gave them maximum freedom to excel in benchmarks, and it was then endorsed by C++. The C definition is that "undefined behavior" can have arbitrary concrete behavior, not that a compiler can assume it does not happen. (that form semantic people prefer the former because it mak…

> The C definition is that "undefined behavior" can have arbitrary concrete behavior, not that a compiler can assume it does not happen. What is the difference between those? How does a compiler that assumes UB never happens violate the requirement that UB can have arbitrary concrete behavior? If we look at a simple example like optimizing "x + y > x" (signed arithmetic, y known to be positive) to "true" -- that will…

"arbitrary concrete behavior" means that at this point anything can happen on the real machine. This implies that everything before this point has to behave according to the specification. "is impossible" is stronger, as the whole program could behave erratically. But having partial correctness is important in a lot of scenarios and this is why we want to have it and in "UB" it is the former and not "impossible".

In the ISO C standard, we use "unspecified" for a non-deterministic choice among clearly specified alternatives. So this is well understood.

Post reply on HN