← Back to context

Comment by pron

3 months ago

I think that describing Zig as a "rewrite of C" (good or otherwise) is as helpful as describing Python as a rewrite of Fortran. Zig does share some things with C - the language is simple and values explicitness - but at its core is one of the most sophisticated (and novel) programming primitives we've ever seen: A general and flexible partial evaluation engine with access to reflection. That makes the similarities to C rather superficial. After all, Zig is as expressive as C++.

> Most importantly, it dodges Rust and C++'s biggest mistake, not passing allocators into containers and functions

I think that is just a symptom of a broader mistake made by C++ and shared by Rust, which is a belief (that was, perhaps, reasonable in the eighties) that we could and should have a language that's good for both low-level and high-level programming, and that resulted in compromises that disappoint both goals.

To me, the fact that Zig has spent so long in development disqualifies it as being a "rewrite of C."

To be clear, I really like Zig. But C is also a relatively simple language to both understand and implement because it doesn't have many features, and the features it does have aren't overly clever. Zig is a pretty easy language to learn, but the presence of comptime ratchets up the implementation difficulty significantly.

A true C successor might be something like Odin. I am admittedly not as tuned into the Odin language as I am Zig, but I get the impression that despite being started six months after Zig, the language is mostly fully implemented as envisioned, and most of the work is now spent polishing the compiler and building out the standard library, tooling and package ecosystem.

  • I don't think it's the implementation that's delaying Zig's stabilisation, but the design. I'm also not sure comptime makes the implementation all that complicated. Lisp macros are more powerful than comptime (comptime is weaker by design) and they don't make Lisp implementation complicated.

    • Fair. I'm not a compiler developer, so I'll defer to your expertise on that front.

      That being said, I suppose my ultimate wonder is how small a Zig implementation could possibly be, if code size and implementation simplicity was the priority. In other words, could a hypothetical version of the Zig language have existed in the 80's or 90's, or was such a language simply out of reach of the computers of the time.

      1 reply →

> Zig does share some things with C - the language is simple and values explicitness - but at its core is one of the most sophisticated (and novel) programming primitives we've ever seen: A general and flexible partial evaluation engine with access to reflection.

To my understanding (and I still haven’t used Zig) the “comptime” inherently (for sufficiently complex cases) leads to library code that needs to be actively tested for potential client use since the instantiation might fail. Which is not the case for the strict subset of “compile time” functionality that Java generics and whatnot bring.

I don’t want that in any “the new X” language. Maybe for experimental languages. But not for Rust or Zig or any other that tries to improve on the mainstream (of whatever nice) status quo.

  • > leads to library code that needs to be actively tested for potential client use since the instantiation might fail

    True, like templates in C++ or macros in C or Rust. Although the code is "tested" at compile time, so at worst your compilation will fail.

    > I don’t want that in any “the new X” language

    Okay, and I don't want any problem of any kind in my language, but unfortunately, there are tradeoffs in programming language design. So the question is what you're getting in exchange for this problem. The answer is that you're getting a language that's both small and easy to inspect and understand. So you can pick having other problems in exchange for not having this one, but you can't pick no problems at all. In fact, you'll often get some variant of this very problem.

    In Java, you can get by with high-level abstractions because we have a JIT, but performance in languages that are compiled AOT is more complicated. So, in addition to generics, low-level languages have other features that are not needed in Java. C++ has templates, which are a little more general than generics, but they can fail to instantiate, too. It also has preprocessor macros that can fail to compile in a client program. Rust has ordinary generics, which are checked once, but since that's not enough for a low-level language, it also has macros, and those can also fail to expand correctly.

    So in practice, you either have one feature that can fail to compile in the client, or you can have the functionality split among multiple features, resulting in a more complicated language, and still have some of those features exhibit the same problem.

    • I wasn’t clear then. I would rather have N language features of increasing complexity/UX issues for dealing with increasingly complex situations rather than one mechanism to rule them all that can fail to instantiate in all cases (of whatever complexity). That’s the tradeoff that I want.

      Why? Because that leads to better ergonomics for me, in my experience. When library authors can polish the interface with the least powerful mechanism with the best guarantees, I can use it, misuse it, and get decent error messages.

      What I want out of partial evaluation is just the boring 90’s technology of generalized “constant folding”.[1] I in principle don’t care if it is used to implement other things... as long as I don’t have surprising instantiation problems when using library code that perhaps the library author did not anticipate.

      [1]: And Rust’s “const” approach is probably too limited at this stage. For my tastes. But the fallout of generalizing is not my problem so who am I to judge.

      > Okay, and I don't want any problem of any kind in my language, but unfortunately, there are tradeoffs in programming language design.

      I see.

      > So in practice, you either have one feature that can fail to compile in the client, or you can have the functionality split among multiple features, resulting in a more complicated language,

      In my experience Rust being complicated is more of a problem for rustc contributors than it is for me.

      > and still have some of those features exhibit the same problem.

      Which you only use when you need them.

      (I of course indirectly use macros since the standard library is full of them. At least those are nice enough to use. But I might have gotten some weird expansions before, though?)

      That will have to do until there comes along a language where you can write anything interesting as library code and still expose a nice to use interface.

      1 reply →