Recent submissions

Comment · 1 year ago
> "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".

So that rules out "time-traveling UB", but it would still permit optimizing "x+y < x" to "false" for non-negative y, right? I can't tell if you think that that is a legal transformation or not, and I'd be curious to know. :)

FWIW I agree we shouldn't let UB time-travel. We should say that all observable events until the point of UB must be preserved. AFAIK that is e.g. what CompCert does. But I would still describe that as "the compiler may assume that UB does not happen" (and CompCert makes use of that assumption for its optimizations), so I don't understand the distinction you are making.

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

Except for "unspecified value" which apparently can be very different from just non-deterministically choosing any value (https://www.open-std.org/jtc1/sc22/wg14/www/docs/dr_451.htm). If "unspecified" always meant "normal non-deterministic choice", then this would clearly be a miscompilation: https://godbolt.org/z/9Gqqs7axj.

Quite a few places in the standard just say "result/behavior is unspecified", so the set of alternatives is often not very clear IMO. In particular, when it says that under some condition "the result is unspecified", and let's say the result has integer type, does that mean it non-deterministically picks some "normal" integer value, or can it be an "unspecified value" that behaves more like LLVM undef in that it is distinct from every "normal" value and can violate basic properties like "x == x"?

Comment · 1 year ago
Comment · 1 year ago
> 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.

Comment · 1 year ago
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. :)

Comment · 1 year ago
Comment · 1 year ago
> The rest is your bug; the variable values coming out of sync with each other, not maintaining the invariant among their values.

If the language and its runtime let me break their invariant, then that's their bug, not mine. This is the fundamental promise of type-safe languages: you can't accidentally break the language abstraction.

> It could be the case that a thread-unsafe program breaks a managed run-time, but not an unvarnished truth.

I demonstrated that the Go runtime is such a case, and I think that should be considered a memory safety violation. Not sure which part of that you disagree with...

Comment · 1 year ago
(EDIT: removed the first part since I realized you were replying to some comment further up, not my example.)

> Rust has, unfortunately, changed the narrative so that people now believe memory safety is a property of the language, when it is one of the implementation.

I am not sure I agree with that (the concept of memory-safe languages looong predates Rust), but you can just define a memory-safe language as one where all conforming implementations are memory-safe -- making it a feature of the language itself, not just a feature of a particular implementation.

Comment · 1 year ago
See https://www.youtube.com/watch?v=QrrH2lcl9ew for a a presentation of Google's study, which found no measurable difference in productivity between teams using Rust vs Go.
Comment · 1 year ago
Fair, I misunderstood then. :)
Comment · 1 year ago
> The perception created by your article is that people shouldn't use Go because it's not memory-safe.

Uh, where exactly am I saying or implying that? I am, in fact, saying that Go is much closer to memory-safe languages than to C, safety-wise.

But I am arguing that the term "memory safe" should only be used for languages that actually went through the effort of thinking this problem through to the end and plugging all the holes through which memory safety violates can sneak in. Go is 99% there, but it's falling slightly short of the goal. I think that's a useful distinction, and I am disappointed that it is regularly swept under the rug, which is why I wrote this blog post. You are free to disagree, I never expected to convince everyone. But I think I gave some people some new food for thought, and that's all I can hope for.

Comment · 1 year ago
> 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.

Comment · 1 year ago
Yeah, Go is often listed with memory-safe languages, I know that. And yet when people define memory safety, Go usually fails to satisfy that definition. That's why I was asking for a definition of memory safety that would include Go.
Comment · 1 year ago
You keep making arguments by assertion without giving sources, so :shrug: yeah this isn't going to go anywhere.

I think we actually agree on all of the factual points here, we just don't agree on how languages should be categorized/labeled according to their guarantees in both a theoretical and a practical sense, and that's largely a subjective matter anyway. So, happy to agree to disagree here.

Comment · 1 year ago
Can you point me to the Go definition of memory safety? I searched all over their website, and couldn't find any.

(But also, it'd be kind of silly for every language to make up their own definition of memory safety. Then even C is memory safe, they just have to define it the right way. ;)

Comment · 1 year ago

Live from the official Hacker News API

A modern reader for Hacker News · SvelteKit + Skeleton