Hacker News new | ask | show | jobs
by pron 315 days ago
> Maybe if someone bends over backwards to rationalize it, but not in any real sense.

In a simple, real sense. Zig prevents out-of-bounds access just as Rust does; C++ doesn't. Interestingly, almost all of Rust's complexity is invested in the less dangerous kind of memory unsafety (https://cwe.mitre.org/top25/archive/2024/2024_cwe_top25.html).

> You can't build RAII and moves into zig.

So RAII is part of the definition of memory safety now?

Why not just declare memory safety to be "whatever Rust does", say that anything that isn't exactly that is worthless, and be done with that, since that's the level of the arguments anyway.

We could, of course, argue over which of Rust, Zig, and C++ offers the best contribution to correctness beyond the sound guarantees they make, except these are empirical arguments with little empirical data to make any determination, which is part of my point.

Software correctness is such a complicated topic and, if anything, it's become more, not less, mysterious over the decades (see Tony Hoare's astonishment that unsound methods have proven more effective than sound methods in many regards). It's now understood to be a complicated game of confidence vs cost that depends on a great many factors. Those who claim to have definitive solutions don't know what they're talking about (or are making unfounded extrapolations).

1 comments

C++ doesn't.

Then why do my data structures detect if I go out of bounds?

Interestingly, almost all of Rust's complexity is invested in the less dangerous kind of memory unsafety

I didn't say anything about rust.

So RAII is part of the definition of memory safety now?

Yes. You can clean up memory allocations automatically with destructors and have value semantics for memory that is on the heap.

Why not just declare memory safety to be "whatever Rust does", say that anything that isn't exactly that is worthless, and be done with that, since that's the level of the arguments anyway.

Why are you talking about rust here? Focus on what I'm saying.

We could, of course, argue over which of Rust, Zig, and C++

if anything, it's become more, not less, mysterious over the decades

Says who?

I don't care about rust or zig, I'm saying that these are solved problems in C++ and I don't have to deal with them. Zig does not have destructors and move semantics.

> Then why do my data structures detect if I go out of bounds?

Because you have iterator debugging and/or assertions turned on and are only using non-primitive data structures (e.g. std::vector, std::array).

Zig does the thing that Rust and Go do where it makes the primary primitive for pointers to chunks of memory (slices) bounds checked. You can opt out with optimization settings, but I think most programs will build in "safe release" mode unless they're very confident in their test coverage.

It's strictly better than C++, because in practice codebases are passing lots of `(data, len)` params around no matter how strongly you emphasize in your style guide to use `std::span`. The path of least resistance in Zig, including the memory allocator interface, bundles in language-level bounds checking.

>I think most programs will build in "safe release" mode

Do you have any citations to support this 'safe release' theory? Like there are not many Zig applications and not many of them document their decisions. One i could find [1] does not mention safe anywhere.

1. https://ghostty.org/docs/install/build

Ghostty is trying to be a speed demon terminal, so I'd expect it to use ReleaseFast.

The current build system docs don't prioritize one build mode over another:

https://ziglang.org/learn/build-system/

> Standard optimization options allow the person running zig build to select between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. By default none of the release options are considered the preferable choice by the build script, and the user must make a decision in order to create a release build.

But for more opinionated recommendations, ReleaseSafe is clearly favored:

https://zig.news/kristoff/how-to-release-your-zig-applicatio...

> ReleaseSafe should be considered the main mode to be used for releases: it applies optimizations but still maintains certain safety checks (eg overflow and array out of bound) that are absolutely worth the overhead when releasing software that deals with tricky sources of input (eg, the internet).

https://zighelp.org/chapter-3/

> Users are recommended to develop their software with runtime safety enabled, despite its small speed disadvantage.

If you could somehow collect real-world data, the overwhelming majority of Zig programs aren't released and have likely only made debug builds :P

Memory leaks are unrelated to memory safety. That is to say, code that leaks memory is memory safe. So I'm not sure what RAII is supposed to help with.

A problem not solved in C++ is the need to reserve a single bit-pattern per type that can be moved from, to indicate that it has been moved from (and is not a valid value for any other purpose).

> Then why do my data structures detect if I go out of bounds?

I didn't mean you can't write C++ code that enforces that, I said C++ itself doesn't enforce it.

> Yes. You can clean up memory allocations automatically with destructors and have value semantics for memory that is on the heap.

Surely there are other ways to do that. E.g. Zig has defer. You can say that you may forget to write defer, which is true, but the implicitness of RAII has cause (me, at least) many problems over the years. It's a pros-and-cons thing, and Zig chooses the side of explicitness.

> Why are you talking about rust here? Focus on what I'm saying.

You're right, sorry :)

> Says who?

Says most people in the field of software correctness (and me https://pron.github.io). In the seventies, the prevalent opinion was that proofs of correctness would be the only viable approach to correctness. Since then, we've learnt two things, both of which were surprising.

The first was new results in the computational complexity of model checking (not to be confused with the computational complexity of model checkers; we're talking about the intrinsic computation complexity of the model checking problem, i.e. the problem of knowing whether a program satisfies some correctness property, regardless of how we learn that). This included results (e.g. by Philippe Schnoebelen) showing that even though there would be the reasonable expectation that language abstractions could make the problem easier, even in the worst case - it doesn't.

The second was that unsound techniques, including engineering best practices, have proven far more effective than was thought possible in the seventies. This came as quite a shock to formal methods people (most famously, Tony Hoare, who wrote a famous paper about it).

As a result, the field of software correctness has shifted its main focus from proving program correct to finding interesting confidence/cost tradeoffs to reduce the number of bugs, realising that there's no single best path to more correctness (as far as we know today).

> I'm saying that these are solved problems in C++ and I don't have to deal with them. Zig does not have destructors and move semantics.

That's true, but these are not memory safety guarantees. These are mechanisms that could mitigate bugs (though perhaps cause others), and Zig has other, different mechanisms to mitigate bugs (though perhaps cause others). E.g. see how easy it is to write a type-safe printf in Zig compared to C++, or how Zig handles various numeric overflow issues compared to C++. So it's true that C++ has some features we may find helpful that Zig doesn't and vice-versa, we can't judge which of them leads to more correct programs. All I said was that Zig offers more safety guarantees than C++, which it does.

Zig has defer.

And C has free, but you have to remember to use it and use it correctly every single time instead of the memory working by default with no intervention.

Says most people in the field of software correctness

Not true, the last 30 years have had much safer languages than before java, scripting languages, modern C++ and rust.

That's true, but these are not memory safety guarantees.

Pragmatically they mean you don't have to worry about bounds checking or memory deallocation and it stops being a problem. Zig doesn't have this and it doesn't have safety guarantees either.

>C has free; zig has defer

But even in a c++ destructer if you forget to dealloc a private heap allocation ... then you're in the same darn place.

And if you divide by 0 your program still crashes. The reality is that you can use vectors for memory allocations and you never have to worry about it. If you do need to wrap resource allocation, you do it once, test it and it will probably work from then on. This is much better than the alternative of having to remember to free the memory, close the file, unlock the mutex correctly every single time you need one.
I don't agree that "you never have to worry about it" unless you're also using smart pointers, which is rarely what I want.

I think that the alternative where all allocations and deallocations are made clear (in Zig, allocating routines are "coloured" by convention) is the better alternative, at least for the kind of low-level programming I do and for my way of thinking about low-level code.

When I write code where I don't want to see or worry about each implementation detail or see exactly where and when each operation is executed, I use Java.

> And C has free, but you have to remember to use it and use it correctly every single time instead of the memory working by default with no intervention.

Tangential, but memory leaks are not considered a safety issue, especially by those who do like to contrast with Rust (as it isn't prevented in Rust).

If we're talking about features that help (though not completely avoid) some bugs, you can't just consider the features C++ has and Zig doesn't, but also consider the relevant features Zig has and C++ doesn't.

Like I said, I don't know which of those two languages results in more correct programs (just as I don't know the answer for Zig vs Rust), but I do know that Zig offers more safety guanrantees than C++, and Rust offers more safety guarantees than Zig. I certainly don't claim that more safety guarantess always equals more correctness at a lower cost.

Even more tangentially, in the Java world we have this thing called "integrity" (https://openjdk.org/jeps/8305968) which is the ability of Java code to locally establish inviolate invariants that are guaranteed to hold globally (unless the application author - importantly not any library code - explicitly allows them to be violated). C++ scores quite low on the integrity front, as virtually all intended invariants can be violated without a global flag, sometimes in ways that are hard to detect. In both Rust and Zig, integrity violations are generally easier to at least detect (although in Zig they're sometimes harder to establish in the first place; this is intentional, and I don't entirely agree with the justification for that, although I can see its merits in a low-level language).

> Not true, the last 30 years have had much safer languages than before java, scripting languages, modern C++ and rust.

I don't see how that contradicts what I said, especially since language that offer even more correctness - such as Idris or ATS - have had effectively zero adoption. The languages that have succeeded are safer than C or FORTRAN, but also clearly compromise on what they offer (compared to Idris/ATS) because of costs. They very much embody the an acceptance of tradeoffs, and much of the memory safety in most safe languages is offered through GCs, that come with the cost of higher memory footprint. If anything, their growing popularity has come due to advancements in GCs.

Rust (you brought it up this time) is particularly interesting, because it offers something different than before to prevent UAF but at a higher cost than previous popular safe languages. While I don't know how popular Rust will be in the future, its current adoption is quite significantly lower than any language that's ever become popular at the same age.

> Pragmatically they mean you don't have to worry about bounds checking or memory deallocation and it stops being a problem

I haven't noticed that either one of these has "stopped being a problem", and I think that those who either sell or buy Rust do so because they believe these are still significant problems in C++ (and I would agree, except I think there are worse problems in C++ - that Rust, unfortunately, adopted - even with respect to correctness, that Zig attempts to solve).

> Zig doesn't have this and it doesn't have safety guarantees either

Zig definitely has safety guarantees around bounds and numeric overflow that C++ doesn't.

memory leaks are not considered a safety issue

Who told you that?

in the Java world we have this thing called "integrity"

Your claim was that zig is 'safer' than C++

Zig definitely has safety guarantees around bounds and numeric overflow that C++ doesn't.

This can be built in to a class too if someone really wants a bunch of branching in their math.

It seems like now safety is being redefined to say that memory leaks don't count and numeric overflow needs to be done like zig. If your program leaks memory, it eventually crashes if it runs indefinitely and that means you need to free memory, which means you need to free it at the right time only once.

> Who told you that?

There is no one definitive definition of memory safety, but it generally refers to things that can lead to undefined behaviour (in the C and C++ sense), usually due to "type confusion" (or sometimes "heap pollution"), i.e. referencing an address of memory that contains data of one type as if it were another, which can happen due to both bounds or UAF violations. Memory leaks don't cause undefined behaviour.

> This can be built in to a class too if someone really wants a bunch of branching in their math.

Let me say this again: The Zig language, just like Rust, guarantees that there are no bounds violations (except in syntactically demarcated unsafe code). C++ just doesn't do that.

That is not to say that the lack of this guarantee in C++ means you can't write correct programs in C++ as easily as in Zig or in Rust, but it is, nevertheless, a difference in the guarantees made by the language.

> It seems like now safety is being redefined to say that memory leaks don't count and numeric overflow needs to be done like zig

Memory unsafety is generally considered to be some subset of undefined behaviour (possibly including all undefined behaviour). Out-of-memory and stack overflow errors are definitely problems, but as they don't cause undefined behaviour (well, depending on stack protection) they're not usually regarded in the class of properties called memory safety.

Numeric overflows, on the other hand, might also not be regarded as memory safety, but they are very much undefined behaviour in both C and C++.