Hacker News new | ask | show | jobs
by judofyr 315 days ago
This is one the reasons I find it so silly when people disregard Zig «because it’s just another memory unsafe language»: There’s plenty of innovation within Zig, especially related to comptime and metaprogramming. I really hope other languages are paying attention and steals some of these ideas.

«inline else» is also very powerful tool to easily abstract away code with no runtime cost.

10 comments

What I’ve seen isn’t people disregarding Zig because it’s just another memory-unsafe language, but rather disqualifying Zig because it’s memory-unsafe, and they don’t want to deal with that, even if some other aspects of the language are rather interesting and compelling. But once you’re sold on memory safety, it’s hard to go back.
This is really the crust of the argument. I absolutely love the Rust compiler for example, going back to Zig would feel a regression to me. There is a whole class of bugs that my brain now assumes the compiler will handle for me.
Problem is, like they say the stock market has predicted nine of the last five recessions, the Rust compiler stops nine of every five memory safety issues. Put another way, while both Rust and Zig prevent memory safety issues, Zig does it with false negatives while Rust does it with false positives. This is by necessity when using the type system for that job, but it does come at a cost that disqualifies Rust for others...

Nobody knows whether Rust and/or Zig themselves are the future of low-level programming, but I think it's likely that the future of low-level programming is that programmers who prefer one approach would use a Rust-like language, while those who prefer the other approach would use a Zig-like language. It will be intesting to see whether the preferences are evenly split, though, or one of them has a clear majority support.

C++ already illustrates this idea you're talking about and we know exactly where this goes. Rust's false positives are annoying, so programmers are encouraged to further improve the borrowck and language features to reduce them. But the C++ or Zig false negatives just means your program malfunctions in unspecified ways and you may not even notice, so programmers are encouraged to introduce more and more such cases to the compiler.

The drift over time is predictable, compared to ten years ago Rust has fewer false positives, C++ has more false negatives.

You are correct to observe that there is no middle choice here, that's Rice's Theorem, non-trivial semantic correctness is Undecidable. But I would argue we already know what you're calling the "false positive" scenario is also not useful, we're just not at the point where people stop doing it anyway.

> C++ already illustrates this idea you're talking about and we know exactly where this goes.

No, it doesn't. Zig is safer than C++ (and it's much simpler, which also has an effect on correctness).

Making up some binary distinction and then deciding that because C++ falls on the same side of it as Zig (except it doesn't, because Zig eliminates out-of-bounds access to the same degree as Rust, not C++) then what applies to one must apply to the other. There is simply no justification to make that equivalence.

> There is no middle choice here, that's Rice's Theorem, non-trivial semantic correctness is Undecidable.

That's nothing to do with Rice's theorem. Proving some properties with the type system isn't a general algorithm; it's a proof you have to work for in every program you write individually. There are languages (Idris, ATS) that allow you to prove any correctness property using the type system, with no false positives. It's a matter of the effort required, and there's nothing binary about that.

To get a sense of the theoretical effort (the practical effort is something to be measured empirically, over time) consider the set of all C programs and the effort it would take to rewrite an arbitrary selection of them in Rust (while maintaining similar performance and footprint characteristics). I believe the effort is larger than doing the same to translate a JS program to a Haskell program.

> There is simply no justification to make that equivalence.

I explained in some detail exactly why this equivalence exists. I actually have a small hope that this time there are enough people who think it's a bad idea that we don't have to watch this play out for decades before the realisation as we did with C and C++.

Yes it's exactly Rice's Theorem, it's that simple and that drastic. You can choose what to do when you're not sure, but you can't choose (no matter how much effort you imagine applying) to always be sure†, that Undecidability is what Henry Rice proved. The languages you mention choose to treat "not sure" the same as "nope", like Rust does, you apparently prefer languages like Zig or C++ which instead treat "not sure" as "it's fine". I have explained why that's a terrible idea already.

The underlying fault, which is why I'm confident this reproduces, is in humans. To err is human. We are going to make mistakes and under the Rust model we will curse, perhaps blame the compiler, or the machine, and fix our mistake. In C++ or Zig our mistake compiles just fine and now the software is worse.

† For general purpose languages. One clever trick here is that you can just not be a general purpose language. Trivial semantic properties are easily decided, so if your language can make the desired properties trivial then there's no checking and Rice's Theorem doesn't apply. The easy example is, if my language has no looping type features, no recursive calls, nothing like that, all its programs trivially halt - a property we obviously can't decidably check in a general purpose language.

What is your reason to claim zig is safer than c++?
> Zig is safer than C++

Maybe if someone bends over backwards to rationalize it, but not in any real sense. Zig doesn't have automatic memory management or move semantics.

In C++ you can put bounds checking in your data structures and it is already in the standard data structures. You can't build RAII and moves into zig.

Unless you actually use the simplicity to apply formal methods I don't think simplicity make a language safer. The exact opposite. You can see it play out in the C vs C++ arena. C++ is essentially just a more complex C. But I trust modern C++ much more in terms of memory safety.
So far I think the adoption in critical infrastructure (Linux, AWS, Windows, etc.) is clearly in Rust favor but I agree that something at some point will replace Rust. My belief is that more guardrails will end up winning no matter the language since the last 50 years of progamming have shown us we can't rely on humans to write bug free code and it is even worse with LLM.
I think the problem with this attitude is the compiler becomes a middle manager you have to appease rather than a collaborator. Certainly there are advantages to having a manager, but if you go off the beaten track with Rust, you will not have a good time. I write most of my code in Zig these days and I think being able to segfault is a small price to pay to never have to see `Arc<RefCell<Foo<Bar<Whatever>>>` again.
I view it as a wonderful collaborator, it tells me automatically were my code is wrong and it gets better with every release, I can't complain really. I think a segfault is a big price to pay, but it depends on the criticality of it I guess.
Not to mention that `Arc` uses a GC (and not a stellar one, at that)...
You can use alternative GC such as crossbeam if you want. You're not locked into using an Arc.
Lol, what are you even trying to say here?

Is Zig such an amazing language that while using it you won't ever need reference-counted pointers?

You can write rust without over-using traits. Regrettably, many rust libs and domains encourage patterns like that. One of the two biggest drawbacks of the rust ecosystem.
I can't imagine writing c++ or c these days without static analysis or the various llvm sanitizers. I would think the same applies to zig. Rather than need these additional tools, rust gives you most of their benefits in the compiler. Being able to write bugs and have the code run isn't really something to boast about.
I would rather rely on a bunch of sanitizers and static analysis because it is more representative of the core problem I am solving: Producing machine code. If I want Rust to solve these problems for me I now have to write code in the Rust model, which is a layer of indirection that I have found more trouble than it's worth.
How do you guard concurrent access in your multithreaded code?

Due diligence every single time after the tenth refactor?

In practice, almost all memory safety related bugs caught by the Rust compiler are caught by the Zig safe build modes at run time. This is strictly worse in isolation, but when you factor in the fact that the rest of the language is much easier to reason about, the better C interop, the simple yet powerful metaprogramming, and the great built in testing tools, the tradeoffs start to become a lot more interesting.
catching at compile time is much better, though. there are plenty of strange situations that can happen that you'll not reach in runtime (for example, odds of running into a tripwire increase over time, things that can only happen after certain amount of memory fragmentation -- maybe you forgot an errdefer somewhere, etc.)
Nit: I think you want crux in that phrase, not crust.
Thanks! Cant edit anymore, I guess I was feeling hungry this morning
would you be satisfied if there was a static safety checker? (or if it were a compiler plugin that you trigger by running a slightly different command?). Note that zig compiles as a single object, so if you import a library and the library author does not do safety checking, your program would still do the safety checking if it doesn't cross a C abi boundary.

https://www.youtube.com/watch?v=ZY_Z-aGbYm8

As someone who uses D and has been doing things like what you see in the post for a long time, I wonder why other languages would put attention to these tricks and steal them when they have been completely ignoring them forever when done in D. Perhaps Zig will make these features more popular, but I'm skeptic.
I was trying to implement this trick in D using basic enum, but couldn't find a solution that works at compile-time, like in Zig. Could you show how to do that?

  import std.meta: AliasSeq;

  enum E { a, b, c }

  void handle(E e)
  {
      // Need label to break out of 'static foreach'
      Lswitch: final switch (e)
      {
          static foreach (ab; AliasSeq!(E.a, E.b))
          {
              case ab:
                  handleAB();
                  // No comptime switch in D
                  static if (ab == E.a)
                      handleA();
                  else static if (ab == E.b)
                      handleB();
                  else
                      static assert(false, "unreachable");
                  break Lswitch;
          }
          case E.c:
              handleC();
              break;
      }
  }
Thanks! That indeed does the equivalent as the Zig code... but feels a bit pointless to do that in D, I think?

Could've done this and be as safe, but perhaps it loses the point of the article:

    enum U { A, B, C }

    void handle(U e)
    {
      with (U)
        final switch (e) {
        case A, B:
          handleAB();
          if (e == A) handleA(); else handleB();
          break;
        case C:
          handleC();
          break;
        }
    }
This perspective that many people take on memory-safety of Rust seems really "interesting".

Unfortunately for all fanatics, language really doesn't matter that much.

I have been using KDE for years now and it works perfectly good for me. It has no issues/crashes, it has many features in terms of desktop environment and also many programs that come with it like music player, video player, text editor, terminal etc. and they all work perfectly well for me. Almost all of this is written in C++. No need to mention the classic linux/chromium etc. etc which are all written in c++/c.

I use Ghostty which is written in zig, it is amazingly polished and works super well as well.

I have built and used a lot of software written in Rust as well and they worked really well too.

At some point you have to admit, what matters is the people writing software, the amount of effort that goes into it etc. it is not the langauge.

As far as memory-safety goes, it really isn't close to being the most important thing unless you are writing security critical stuff. Even then just using Rust isn't as good as you might think, I uncountered a decent amount of segfaults, random crashes etc. using very popular Rust libraries as well. In the end just need to put in the effort.

I'm not saying language doesn't matter but it isn't even close to being the most important thing.

> As far as memory-safety goes, it really isn't close to being the most important thing unless you are writing security critical stuff.

Safety is the selling point of Rust, but it's not the only benefit from a technical point of view.

The language semantics force you to write programs in a way that is most convenient for the optimizing compiler.

Not always, but in many cases, it's likely that a program written in Rust will be highly and deeply optimized. Of course, you can follow the same rules in C or Zig, but you would have to control more things manually, and you'd always have to think about what the compiler is doing under the hood.

It's true that neither safety nor performance are critical for many applications, but from this perspective, you could just use a high-level environment such as the JVM. The JVM is already very safe, just less performant.

> just another memory unsafe language

Also, treating all languages that don't ensure full memory safety as if they're equally problematic is silly. The reason not ensuring memory safety is bad is because memory unsafety as at the root of some bugs that are both common, dangerous, and hard to catch. Only not all kinds of memory unsafety are equally problematic, Zig does ensure the lack of the the most dangerous kind of unsafety (out-of-bounds access) while making the other kind (use-after-free) easier to find.

That the distinction between "fully memory safe" and "not fully memory safe" is binary is also silly not just because of the above, but because no lanugage, not even Java, is truly "fully memory safe", as programs continue to employ components not written in memory safe languages.

Furthermore, Zig has (or intends to have) novel features (among low-level languages) that help reduce bugs beyond those caused by memory unsafety.

If you one day write a blog, I would want to subscribe.

Your writing feels accessible. I find it makes complex topics approachable. Or at least, it gives me a feel of concepts that I would otherwise have no grasp on. Other online writing tends to be permeated by a thick lattice of ideology or hyper-technical arcanery that inhibits understanding.

> Your writing feels accessible. I find it makes complex topics approachable

Yeah. By omitting a large swath of nuance. It reeks of "you can approximate cow with a sphere the size of Jupiter". It's baffling ludicrous.

Any rhetorical device that equates Java/C# (any memory safe Turing language ) safety with C is most likely a fallacy.

> Any rhetorical device that equates Java/C# (any memory safe Turing language ) safety with C is most likely a fallacy.

I agree, but I didn't do any of that. If anything my point was that 1. safety is clearly not a binary thing and no one really treats it as such (even those who claim it is a binary distinction) and 2. that trying to extrapolate from one language to another based on choosing some property that we think is the most relevant one may be assuming that which we seek to prove.

Saying that C, C++, and Zig are "the same" because they all make fewer guanratees than Rust is as silly as saying C, C++, Zig, and Rust are the same because they all offer fewer guarantees than ATS, or that Rust and Java are the same because they offer similar guarantees but with very different complexity costs.

Also, the focus on memory safety is justified because of the security bugs it causes, but the two major kinds of unsafety (out-of-bounds access and use-after free) aren't equally dangerous, and Rust pays most of its complexity cost to prevent the less dangerous of the two (https://cwe.mitre.org/top25/archive/2024/2024_cwe_top25.html). There's even more nuance here, because some techniques focus on reducing the risk of exploitable use-after-free bugs without preventing it or even making it easier to detect at all (https://www.cl.cam.ac.uk/~tmj32/papers/docs/ainsworth20-sp.p...).

It's all a matter of degree, both when it comes to the risk as well as to the cost of avoiding it. Not much here, beyond the very basics, is simple or obvious.

If you want to read some more even nuanced things I've written about software correctness, you can find some old stuff here: https://pron.github.io

I interpreted his post as saying it's not binary safe/unsafe, but rather a spectrum, with Java safer than C because of particular features that have pros and cons, not because of a magic free safe/unsafe switch. He's advocating for more nuance, not less.
Yeah, it's not binary; it's just a step function. /s

No, it's as close to binary as you can get. Is your only source of Undefined Behavior FFI specially marked functions and/or packages? Have you checked data races for violating thread safety invariants? If yes - You're safe.

Allow a bit of unsafety into the system, like Go, and the unsafety can creep into your ecosystem. See https://www.ralfj.de/blog/2025/07/24/memory-safety.html

Is Go in mostly safer than C++? Maybe. But you can never prove that about either of them. So while you may pretend one is safer than the other, it's a bit like picking which boat is taking on more water.

Can you prove Rust code is safe? Well there is the simple way - no unsafe. But what about unsafe blocks? Yes, you can prove it for them as well. If the unsafe code block is it will note safety invariants and why are they preserved by unsafe block. Can this be practically done? Depends on the crate, but with enough effort, yes.

> Is Go in mostly safer than C++? Maybe

Maybe? You forgot /s there? Asking if Go is mostly safer than C++ is like asking if child proof caps are mostly safer than mason jars for medicine.

> https://www.ralfj.de/blog/2025/07/24/memory-safety.html

Can you show RCE using this? Because, to this day, no one has been able to show me a reasonable program that someone would write and that would result in RCE from "Go memory unsafety" presented in this article. Meanwhile, I can show you thousands of examples and CVEs of how you can easily get RCE using C++.

> Can you prove Rust code is safe? Well there is the simple way - no unsafe. But what about unsafe blocks? Yes, you can prove it for them as well. If the unsafe code block is it will note safety invariants and why are they preserved by unsafe block. Can this be practically done? Depends on the crate, but with enough effort, yes.

You can’t prove Rust code "safe" in the absolute. Safety guarantees apply to safe Rust under the language’s (still evolving) rules, and even then the compiler/backend must uphold them. We still hit unsoundness[1] and miscompiles in safe code (equal pointers comparing unequal... [2]), and the official unsafe code guidelines are not a finalized spec. So documenting invariants in unsafe helps a lot, but it’s not a formal proof, especially across crates and compiler versions.

1. https://github.com/rust-lang/rust/issues/107975

2. https://github.com/rust-lang/rust/labels/I-unsound

On the safety spectrum: C/C++ -> Zig -> Go -> Rust

^ This comment demonstrates what I meant by "thick lattice of ideology"
Thank you!

I did have one once (https://pron.github.io) but I don't know how accessible it is :) (two post series are book-length)

Right but I think people are disappointed because we finally got a language that has memory safety without GC, so Zig seems like a step backwards. Even if it is much much better than C (clearly), it's hard to get excited about a language that "unsolves" a longstanding problem.

> not even Java, is truly "fully memory safe", as programs continue to employ components not written in memory safe languages.

This is a silly point.

> I think people are disappointed because we finally got a language that has memory safety without GC, so Zig seems like a step backwards

Memory safety (like soundly ensuring any non-trivial property) must come at a cost (that's just complexity theory). You can pay for it with added footprint (Java) or with added effort (Rust). Some people are disappointed that Zig offer more safety than C++ but less than Rust in exchange for other important benefits, while others are disappointed that the price you have to pay for even more safety in Rust is not a price they're happy to pay.

BTW, many Rust programs do use GC (that's what Rc/Arc are), it's just one that optimises for footprint rather than speed (which is definitely okay when you don't use the GC as much as in Java, but it's not really "without GC", either, when many programs do rely on GC to some extent).

> This is a silly point.

Why? It shows that even those who wish to make the distinction seem binary themselves accept that it isn't, and really believe that it matters just how much risk you take and how much you pay to reduce it.

(You could even point out that memory corruption can occur at the hardware level, so not only is the promise of zero memory corruption not necessarily worth any price, but it is also unattainable, even in principle, and if that were truly the binary line, then all of software is on the same side of it.)

> You can pay for it with added footprint (Java) or with added effort (Rust)

... or runtime errors (C, Zig presumably).

Ok Zig is clearly better than C in that regard but I think it remains to be seen if it is better enough.

> many Rust programs do use GC (that's what Rc/Arc are)

This is not what most people mean when they say GC.

> Why?

Because when we're talking about the memory safety of a language we're talking about the code you write in that language (and excluding explicit opt-in to memory unsafe behaviour, e.g. `unsafe` or Python's `ctypes`).

Saying "Java isn't memory safe because you can call C" is like saying "bicycles can fly because you can put them on a plane".

If you make advancements but disregard the advancements that came before you, you have a research language, not a modern usable language.
By this definition, every major programming language in use today (C, C++, Java, Python, ...) is merely a research language.
All of your examples were created three decades ago or more.
> «inline else» is also very powerful tool to easily abstract away code with no runtime cost.

Sure, but you lose the clarity of errors. The error wasn't in `comptime unreachable` but in `inline .a .b .c`.

I disagree, I would say the error is in "comptime unreachable" or maybe the whole "switch (ab)".
`comptime unreachable` isn't the underlying cause and not the place you should fix in this case. A good error tells you what caused it, and where to fix it.
Adding a new case is legitimate, failing to handle it (by reaching unreachable) is an error.
Concur. This is a great feature I wish rust had. I've been bitten by the unpleasant syntax this article laments.
I've seen a few new languages come along that were inspired by zig's comptime/metaprogramming in the same language concept.

Zig I think has potential but it hasn't stabilized enough yet for broad adoption. That means it'll be awhile before it's built an ecosystem (libraries, engines etc.) that is useful to developers that don't care about language design.

This isn’t intended as flamebait. I’m trying to understand Zig’s long-term positioning and design philosophy. I have serious confusion about the type of problems Zig is aiming to solve. In my view, Zig is not solving the actual hard problems in systems programming and it doesn't have the foundation to either.

Memory safety? Still entirely manual. Race conditions? Nothing in the language prevents them. There’s no ownership model, no lifetime analysis, no way to tie resource management to the type system. Compare that to Rust’s borrow checker or modern C++’s RAII and concepts. Zig’s type system is shallow. comptime is nice for generating code, but it doesn’t give you formal guarantees or expressive power for invariants, safety, or correctness.

The type system itself has no serious formal grounding. It can’t encode complex invariants, can’t track aliasing, can’t enforce concurrency safety and can’t model safe resource lifetimes. These aren’t academic extras — they’re exactly what decades of research in programming languages, operating systems and concurrent computing tell us you need to scale safety and correctness. Zig ignores them. Performance? When the policy is in the type (allocator choice, borrowing/ownership, fusion shape), Rust/C++ compilers can specialize, inline, and eliminate overhead. In Zig, the same policies are usually runtime values or conventions, which means more indirect calls, more defensive copies and fewer whole-program optimizations.

Concurrency is another major gap and in a real systems language, it cannot be an afterthought. Even if Zig isn’t currently aiming to solve concurrency or safety, a “serious” systems language inevitably has to, because these are the problems that determine scalability, maintainability and security over decades. The async model in Zig is little more than manual coroutine lowering: the compiler rewrites your function into a state machine and leaves correctness entirely to the programmer. There’s no structured concurrency, no safe cancellation, no prevention of shared-state hazards. Without a concurrency model that integrates into the type system, you can’t make guarantees about thread safety or race freedom and you end up relying entirely on discipline (which doesn’t scale).

Even in its most-touted features, Zig seems to be solving syntactic sugar problems, not the important systems problems. defer and errdefer? They’re effectively cleaner syntax for patterns C has had for decades through GNU’s __attribute__((cleanup)) or macro-based scope guards. Error unions? A nice alternative to out-parameters but just syntactic polish over an old idea. comptime? A more integrated macro system but still aimed at reducing boilerplate rather than providing deeper correctness guarantees.

The allocator interface? Another missed opportunity. Zig could have made it type-aware, preventing allocator misuse and catching entire classes of errors at compile time. Instead, it’s basically malloc/free with slightly cleaner function signatures. No safety net, no policy enforcement.

Zig discards decades of research in type systems, concurrency models, safety guarantees, and memory management, then reimplements C with a few ergonomic wins and leaves the hard problems untouched. It’s a restart without the research and not systems language evolution.

I am not a Rust fanatic but by contrast if you’re moving away from C++ or C, Rust actually tackles the big issues. It enforces memory safety without a garbage collector, prevents data races in safe code through its ownership and type system, offers structured concurrency with async/await and has been battle-tested in production for everything from browser engines to operating systems to databases. It is built on decades of progress and integrates those lessons into a language designed to scale correctness and performance together.

In my own code (primarily C++ and Rust), Zig wouldn’t solve a single core problem I face. Memory safety would still be my responsibility, concurrency would still be entirely manual, performance tuning would remain just as challenging and the type system wouldn’t catch the subtle bugs that matter most. The net result would be cosmetic changes paired with fewer correctness guarantees. Even C, for all its flaws, is better defined than Zig (both in having a detailed, standardized specification and in benefiting from partial formalization).

I am eager and optimistic that Zig starts taking itself seriously as a systems language. With new talent, deeper engagement with existing research and a focus on solving the actual hard problems, not just smoothing over C’s syntax, Zig could grow into something much more than it is today. But until then, the question remains: what problems is Zig actually solving that make it worth adopting over Rust or even modern C++? What concrete systems programming problems has Zig’s development team personally run into that shaped its design and are those really the most critical issues worth addressing in a new systems language?

If all it offers is nicer syntax over the same old pitfalls, I don’t see it and I don’t see why anyone betting on long-term systems software should.

I can't take zig as seriously as rust due to lack of data race safety. There are just too many bugs that can happen when you have threads, share state between those threads and manually manage memory. There are so many bugs I've written because I did this wrong for many years but didn't realize until I wrote rust. I don't trust myself or anyone to get this right.