Skip to content

Comment on Being fair about memory safety and performance

Comments

I'm not quite sure who this article is aimed at, and who those "C++ apologists" are, but as someone who programs in C++ all day, doesn't like it at all, and yet won't advocate to switch to Rust, these kinds of arguments are unconvincing. I'm not advocating to switch to Rust not because I don't think it's better than C++. I am absolutely, 100% convinced that Rust is technically better than C++ in most possible ways — in some, significantly better — and worse in almost no way. The problem is that, overall, it's not better enough.

Reevaluating a low-level programming language is something that's done in a large organisation or project once every 15-25 years or so. Switching such a programming language incurs a high cost and a high risk, and is a long-term commitment. To make such a switch, the new language obviously has to be better, but that's not enough. It has to be a hell of a lot better (and, if not, at least return the investment with a profit quickly).

For some, Rust is better enough. For me, not nearly so. Even though it offers a fascinating and, I think, ingenious path to better safety, it shares some of C++'s greatest downside for me, which is that they are both extremely complex languages. Maybe Rust is simpler, but not enough. Rust also shares what I think is C++'s original misguided sin, which is the attempt to create a low level language, whose code appears high-level on the page by means of a lot of implicitness. I've become convinced that that's a very, very bad idea.

If there were no other ideas on the horizon or if Rust seemed like a surefire success, it might have been justified to make such a switch, but that's not the case. Rust's low adoption rate in professional settings is not reassuring to "PL-cautious" people like me, and a language like Zig shows that there are other approaches that appeal to me more, and while more revolutionary and ambitious than Rust in its departure from C++'s philosophy, I think it also has the potential to be better enough. Maybe it will make it, and maybe it inspires some other language that will, or maybe other ideas will turn up. Given the risk and commitment, to me it makes sense to wait. I don't like C++; I believe Rust is better. But that's not enough.

Every large project with years of code (legacy or well maintained) will be hardly ever re-written with complete success. It's not a property of programming language in my opinion. It's just economic sense. I doubt JDK will be rewritten in Rust/Zig, they did GraalVM in Java though.

Zig might be revolutionary but personally for me Rust is why I can dare write non gc code. I never thought I would ever be robotically precise with memory management on a large project in memory unsafe languages, that doesn't change with Zig. There are experts in C/C++, there will be experts in Zig and we will continue having memory safety CVEs because they don't make mistakes/they can use some other static analysis tool/they can test :)

I think people comfortable writing low level code under appreciate what Rust has done for bystanders or newcomers or a large team. It is definitely complex but choosing C/C++/Zig over it would make even less sense since lack of guardrails.

Even if you look just at sound language guarantees, Zig is closer to Rust in terms of memory safety than to C or C++. That it doesn't make all of Rust's static guarantees doesn't put it in the same bucket as languages that make none. Once you reduce memory safety issues to below half of their current rate, there are different paths to achieving a good overall correctness story.

Zig is no way closer to Rust than C or C++ in this regard. Zig is in fact not appreciably safer than C or C++. All of the same classes of memory safety problems that are present in C or C++ are present in Zig.

I am extremely doubtful of the claim that Zig eliminates 50% or more of memory safety problems.

As (safe) Zig eliminates overflows just as (safe) Rust does, and, like Rust, has not unsafe casts, I don't see how "all of the same classes of memory safety problems that are present in C or C++ are present in Zig." Also, Zig guarantees — just as Rust does — that all pointers and means of creating them are known, and precisely so, so analysis tools could work on Zig better than they do on C or C++. In C/C++, at best they could check pointers at the time of dereferencing, while for Zig they could do so at the time of deallocation. You once said that couldn't work because Zig would have dangling pointers lying around (presumably in addition to the one used for deallocation) because C does, but it might as well be the case that C programs leave dangling pointers because tools cannot detect them, which has affected the programming style, not the other way around.

It's OK to doubt claims/hopes about Zig, just as I doubt the claim that Rust can achieve more correct/secure programs than Zig for less effort. Without empirical data, there's really no way to know. So in the meantime, all there is to go on is personal appeal. But you cannot support your claim that Zig is "just like C" with the assumption that it is.

I think that, at the end of the day, there is what we might call an "ideological" difference between us. While we both accept that both language features and best practices — code reviews, tests, tools — reduce bugs, we draw the line of where it's worth to sacrifice one for the other in different places. It might also be the case that Rust's complexity doesn't sacrifice anything for you, but it does for me, as I'm uncomfortable with complex languages, and I think Rust easily makes the top four most complex "production" languages in history (together with C++, Ada, and Scala). So while even for me C is on the wrong side of my line (just as Idris is on the wrong side of yours), I reject extrapolating from C to Zig, because Zig makes many more guarantees at the language level, so without pertinent data about Zig, the question cannot be settled. If I thought Zig was "like C," I wouldn't have found it promising and so intriguing, either.

Having said all that, I don't want it to sound as if I'm willing to bet on Zig right now. I'm far too risk-averse for that. But I wouldn't bet on Rust right now, either. What it brings to the table doesn't offset, for me, its (still-)high risk. All I'm saying is that the revolutionary Zig hints at a promise of a new low-level language that could bring more to the table and justify the risk.

P.S.

I guess we could summarise Rust's and Zig's core design hypotheses as follows: Even though both place the same emphasis on correctness, Rust doesn't compromise on memory safety (which, given what empirical data we do have, is an important component of correctness but certainly not equivalent to it), i.e. it adds all the sound language features needed to provide it, even at the cost of language complexity, while Zig doesn't compromise on language simplicity, i.e. it adds all the sound language features needed to provide memory safety up to the point they impact language complexity. I don't discount the possibility that there might be a language that could be safer than Zig yet less complex than Rust, or perhaps even as soundly-safe as Rust and as simple as Zig, but so far I haven't seen such a language.

Barring any empirical data, we cannot say which, if any, of those two approaches leads to better correctness (where by "better" I mean reaching the desired level of correctness needed for most low-level applications more cheaply), so we both lean on "ideology," where I prefer simplicity whereas you prefer sound guarantees — both of us in the name of correctness. I think we agree that both C and Idris are the wrong paths to correctness, but while we might reasonably disagree on the price we should pay for soundness, placing Zig's memory-safety in the same category as C's is just as exaggerated and misleading as placing Rust's soundness in the same category as Idris's.

By the way, I wouldn't at all be surprised if empirical research ends up finding no significant differences in correctness between the two, and, in fact, would guess it to be the most likely outcome given our inability to find significant bottom-line differences between "reasonable" same-generation languages so far.

Even though both place the same emphasis on correctness

But they don't. At all. Rust treats correctness as paramount, not just memory safety (for instance, the existence of 6 different string types, or the PartialEq/Eq trait dichotomy are for correctness unrelated to memory safety). Zig doesn't.

Sure you can write correct programs in it, and that's what everybody wants, but the language doesn't make any efforts to make it easier than any others. Zig places as much emphasis on correctness as JavaScript[1], it's not C or C++ level of minefield, but when it comes to correctness the language won't help you.

Zig has cool (killer?) features like its seamless integration with existing C code, ease of cross compiling and a super cool metaprograming ability, there's no reason to oversell it on stuff it doesn't focus on: that's the best to disappoint people who'll try it.

In the same vein, talking about “safe Zig” vs safe Rust is misleading to the readers: all Zig is 100% unsafe by default, unless you compile it with ReleaseSafe or add a @setRuntimeSafety, and even if you opt-in to safety, the amount of safety is actually quite limited at the moment. There's a long time goal[2] to check for all kind of UB at runtime when safety checks are enabled, but it doesn't exist yet, and if you look at the afformentioned github issue, you'll see a bunch of “@andrewrk andrewrk removed this from the 0.x.0 milestone, added this to the 0.x+1.0 milestone”. At this point, the final vision of what “safe Zig” will look like isn't known yet! And unless Zig adopts a borrow checker or find an equivalent alternative (which would be super exciting, but is unlikely), it will incur costly runtime checks, making it undesirable in production as it will likely be slower than a regular managed language (it's not useless though, it will be like a better ASAN/UBSAN[3] that you can use during fuzzing, but pretty far from what Rust offers).

[1] and I say that as someone who spend a significant amount of time writing JavaScript for a living.

[2] https://github.com/ziglang/zig/issues/2301

[3] I say “better” because it would be strongly linked with the actual semantics of the languages (which can still change if the development of such tooling requires it) and not the retrofitted best-effort stuff you can have in C.

Rust treats correctness as paramount, not just memory safety

My favorite example of this is that Mutex in Rust is a container type. I think it's very interesting to reflect on why other languages that could do this (basically everything but C and maybe Go) still don't do this. I think it has to do with how you can't stop pointers from escaping the critical section if you don't have a borrow checker.

Rust treats correctness as paramount, not just memory safety... Zig doesn't.

I strongly disagree.

but the language doesn't make any efforts to make it easier than any others.

Of course it does. Correctness is Zig's greatest emphasis, and so the language includes several important correctness features: explicitness (e.g. no overloads, no implicit calls), fast partial compilation, easy testing. Neither Rust nor Javascript have these important correctness features (Comparing Zig's explicit simplicity to JS is just as wrong as comparing its soundness to C. Zig is much sounder than C and much more explicit than JS. Zig is so revolutionary and different from everything else that we have little if anything to compare it to).

It's just that Zig's approach to correctness is different from Rust's. It relies less on soundness and more on simplicity. As someone who's been involved with formal methods for some years, I can tell you that even in that community, there are these two approaches, those who prefer soundness, and those who prefer ease, as two valid approaches to correctness. The soundness camp wants to eliminate certain bugs; the ease camp wants to find as many bugs as possible per unit of effort. I think all agree we need some combination of the two, but where the best sweetspot(s?) is (are?) is an open problem. We do know that there is a tradeoff between the two. To increase ease you must either remove soundness or offer soundness to eliminate a more restricted set of bugs. For example, sound static analysis covers fewer issues than full-blown "deep" sound proofs or model checking in exchange for greater ease, and concolic testing might uncover deep logic bugs, but at the expense of soundness.

all Zig is 100% unsafe by default

That's incorrect.

And unless Zig adopts a borrow checker or find an equivalent alternative...

You are defining the notion of correctness to coincide with what Rust does (i.e. sound memory safety). As Zig's correctness is handled completely differently (broad strokes: find less memory safety bugs, find more others), this makes your point tautological.

Both Rust and Zig have the same emphasis on correctness and the same dedication to features that support it — some help soundness, others ease — but their balance between the two, how they try to achieve correctness and by catching which bugs is different. We don't yet know which, if any, of these languages offer a better path to correctness. And if our assumption is that more soundness always equals more correctness, then neither of these languages are in the right direction, as both intentionally sacrifice a great deal of soundness — almost all of it, really — in the name of ease.

Rust easily makes the top four most complex "production" languages in history (together with C++, Ada, and Scala)

Ada was "complicated" when it was released because it was being compared to C. Contrasting it against contemporary C++ or Rust, Ada 2012 is much simpler.

The language by itself, perhaps not, I don't know.

But, the fact that it has a test framework built in, and the test allocator fails when it detects memory problems (like a built-in valgrind) definitely puts it far above C in terms of making it easy to write correct code.

When I say that a language isn't meaningfully better than C in terms of memory safety, I mean C as programmed in 2022, which very much includes ASan.

You are misinformed. ASAN is a debugging tool. It is not a mechanism to provide safety to C applications.

I think their reply is fair as a response to the capabilities of "zig test"

We're talking about tests here.

I work with ASan for a living. I know what it is, thank you :)

It is the first time, I have heard of zig and I have to say, it indeed does sound nice. (But I haven't done low level programming in quite some time ..)

Can you maybe summarize a bit more, why you prefer it over rust?

The concept of rust, of beeing safe by default and only optimize critical parts sounds solid to me, but after a quick skimming, I have not seen such a feature for zig, too, possibly by design?

" No hidden control flow. No hidden memory allocations. No preprocessor, no macros."

I'm not OP, but the typical answer is that the language is small enough to be understood by any engineer with a few weeks of training, and is pretty easy for anyone familiar with C to grasp the concepts of almost immediately.

This is something that you definitely can't do with C++ and probably can't do with Rust.

For some people it's more comforting to be able to understand the entirety of the language and focus on the complexities of the problem and the implementation than to have a slightly higher level and larger language taking over the details. It's the same reason people tend to like C over C++ or Go over other languages.

This also generally translates into a couple of technical benefits as well, such as much much faster compilers relative to more complicated languages like C++ and Rust.

Personally I still find Rust much more comfortable to write code in generally but I really appreciate the elegance of the Zig approach.

It's mostly a matter of personal taste and values. I am extremely averse to language complexity, I dislike implicitness in low-level languages, and I value fast turnaround times. Others may have different values. I think Rust and Zig might appeal to different people, so you should see if you like it for yourself (it takes 1-2 days to fully learn), but note that it is absolutely not production ready (certainly not for me). I just find its approach refreshing and even surprising, and more in the direction of what I would hope would become the future of low-level programming.

No hidden control flow

Oh boy and I was just reading (https://news.ycombinator.com/item?id=30022022) about ISO C unsuitability for OS programming specially its implicit reordering of the control flow of the code, breaking parts that are sensitive to the machine code generated.

I wonder if that point was introduce as a response for this ISO C nuance, does this means the machine code generated by ziglang code will be explicitly what each statement does?

Rust is technically better than C++ in most possible ways — in some, significantly better — and worse in almost no way.

It's much worse in one way: Interoperation with existing C++ code. Sure, that's not a fair criteria - C++ is designed in a way that makes it almost impossible for other languages to use C++ libraries without a heavyweight wrapper like SWIG. However, even though it's not fair, it's still really important. If you have a project like LLVM with millions of lines of existing C++ code, adding expensive or complicated interop boundaries between different components within your system is not an acceptable price to pay.

Have you tried cxx? C++ interoperability in Rust has come a long way.

cxx is a huge improvement over manually writing bindings in C (with the inherent limitations imposed by C’s lack of expressiveness). At the same time, you still have to write a cxx::bridge to specify every boundary between Rust and C++ code, and the clients of on either side need to code against some generated code, not a simple .rs or .h file. It’s a huge improvement, but it’s still a lot more developer effort and boilerplate to use a C++ class from a Rust file than it would be to use that same C++ class from another C++ file.

cxx also can only handle a subset of the interfaces expressive in C++. If you haven’t written the interface with the specific goal of making it usable via cxx, it’s pretty likely you’ll have issues wrapping it with cxx.

Expecting a language be better "enough" is an unrealistic one, you will be waiting forever.

Of course no language will be perfect to a specific domain, there is no objective metric every individual has their own needs and no universal language can cater all them at once, there will be trade offs.

Rust is already better enough( and possibly will take decades before another language "suceeds" it), choose it or be on the perpetual expectation for a "perfect" language

Expecting a language be better "enough" is an unrealistic one, you will be waiting forever.

Disagree. I think the "market" (the set of programmers) defines "enough". When a language is enough better (in some area, doesn't have to be all areas) you see widespread adoption.

C was enough better than PL/I, ALGOL, and assembly. Java was enough better than C++. (Why? Garbage collection, and the huge standard library.)

So far, Rust is not enough better than C++.

Now, I know this is kind of circular. I'm saying that a language is "enough" better if it wins in the market, and I'm saying that a language wins in the market if it's enough better. But I have some trust in programmers, that they are not just sheep. If a language is better than other languages in a way that matters to actual working programmers, a fair number of them will use it.

I neither a c++ programmer nor a rust one but I read things about programming. My take is that rust is near the breaking point where it becomes so commonly used that more teams and organizations will just start to use it.

The risk of switching for at least parts or a code base will seem low and then it probably looks better enough.

Of course some organizations and code bases are harder to change for some reason or reasons but many will probably start the conversion in the next 5 years. Unless rust just stops growing or something truly better comes along. I personally don't expect something much better soon and I think rust will continue to grow in usage.

My take is that rust is near the breaking point where it becomes so commonly used that more teams and organizations will just start to use it.

You would have said the same thing about PHP or Ruby (or even that they're past their breaking point), and yet companies that are stuck with either one today aren't too happy about it. The problem is that these days, there are a lot of people who can switch languages without much risk. They switch to X, and tomorrow they switch to Y. While it's very good to have low-risk early adopters, having so many of them can really give a false impression about long-term risk. At some point, you need to see many "long-term committers" making a switch.

Some people ask, but how can you have many long-term committers if these risk-averse people also look around for others like them? The answer is that usually this isn't the main factor. If some technology indeed makes a big bottom-line impact, there's a competitive risk in not adopting it (because your competitors will and then beat you in the market), which is why big-benefit technologies spread quickly.

do you think “C” share the same that same miss guided sin?

No, there is very little implicitness in C. However, C suffers from other serious problems that Rust and Zig do address (and C++, too, to a lesser degree). It is extremely unsafe (more so than C++), and has a lower abstraction ability even compared to those other low-level languages.

AboutSource Built by g1lg1l

Hackerly is an independent reader for Hacker News, built on the public HN API. Not affiliated with Y Combinator.