1. 43
  1.  

  2. 23

    Spoiler: the article never directly answers the clickbait question in the title. The article starts with an overview of the problems with Zig’s competitors, then describes various features of Zig. The conclusion of the article is just (paraphrasing) “this article is getting long so I’ll write more about Zig later.”

    1. 31

      That’s technically true, but I would say that this is one of the best articles about programming languages I’ve seen on lobste.rs in a few weeks or months.

      The author demonstrably has experience with C, C++, Swift, Objective C, and Julia, and the ZIg content is quite clearly explained. Better than I’ve seen elsewhere.

      So yes the title has the style of clickbait , but the content is actually good.

      1. 6

        I agree. Was really nice to see a comparison of all those languages in one place and it generally resonates with me.

        I’d love to see some more examples of what the author finds doesn’t “click” with him in Swift though. I really enjoy the language and find it a pity Apple just focused on iOS development.

      2. 12

        I once read somewhere that every headline on articles in print and the internet can be answered with “no” and then you’ve saved yourself a click and a clickbaity article. Applies here as well, next to being on stupid medium.com it’s also not saying much. Lots of words though, not much content.

        1. 17
          1. 3

            Would I have ever remembered that law’s name if you didn’t tell me? ;)

            Anyway, I find it hard to take an article about a C replacement seriously when half the “replacements” it mentions use mandatory garbage collection, and the article doesn’t mention Ada, which is actually used in production, in roles where C is otherwise uncontested, i.e. OS kernels and embedded.

          2. 9

            The question you want to ask yourself is, “Is Betteridge’s Law of Headlines universally correct?” The answer may shock you! Sensitive readers are advised to turn away now.

            Answer is “not quite”. If nothing else because sometimes ad copy starts with a heading like “Is there anything better than Coldbutt™️ branded butt flavoured toothpaste? Our toothpaste flavor exists weigh in.”

          3. 7

            Compared to a lot of the recent thinly-veiled Rust promotional posts, I think this has equal if not more substance.

            1. 4

              Ugh. I wish Lobsters had a flag-topic for clickbait.

              1. 14

                The title is clickbait-ish, but the content is good… I wouldn’t flag this one.

                1. 4

                  Spam is probably the closest flag option

                  1. 13

                    The article is very clearly not spam, so that would be misusing it.

                    1. 1

                      spam being misused for this article is implied by virtue of it being the “closest flag option” and not actually the flag they want to use (because clickbait isn’t an option)

                2. 1

                  I was about to say, can this article’s title be any more clickbaity? Will Zig also increase my girth my 2 inches??

                3. 18

                  It’s not easy to clarify the differences between different programming languages. On paper, all languages are equally capable, but as you start incorporating practical constraints and the actual usage experience, then you see that there is a lot difference between them.

                  Zig has the curse & blessing of being a language that, much like C, lets you program phisical machines, meaning that people that like to inspect the generated machine code will generally be happy with what Zig produces. At the same time Zig offers a few very high level constructs that C and C-like languages don’t usually have, with comptime and async/await being probably the two main examples.

                  The first point (i.e. caring about machine code) is a bit of a lost art for the vast majority of programmers because, in general, web developers, data scientists, and people working in enteprise software development don’t care about this level of detail (add all 3 verticals together and I think you’ll cover > 50% of the programmer population).

                  The good news is that apprecciation for computing architectures is quickly becoming mainstream again, both in terms of physical architectures (e.g. Apple moving to ARM, RISC V, Raspberry PI, …) and virtual ones (e.g. BPF, webassembly).

                  This is also reflected in newer programming languages, where the general idea is to find a compromise between efficiency and ease of use that favors efficiency more than, say, Python or Ruby. That said, there are still many different sweetsposts, Go is one, Rust is another, for example.

                  Zig is hard to describe because historically, low-level languages that want to give you expressive power and higher-level features, do so via a mix of complex type systems and macros, while Zig gives you neither.

                  So this article is basically trying to explain where Zig fits in the programming language landscape. I’ve written my own fair share of Zig articles and I still have trouble finding a succint way of explaining the “whole” Zig story. If you didn’t find this article compelling enough, then I recommend you check out the individual features that Zig offers and try to make up your own mind on whether Zig is interesting or not for you.

                  I personally think that lower-level programming was in long need of a shake-up, from codegen+linking, to metaprogramming, up to concurrency primitives.

                  1. 4

                    I agree it’s a curse and a blessing. I think another way to say it is that many tasks need an applications language rather than a systems language. Zig and Rust are both firmly systems languages in my mind.

                    Writing a shell has been something that was universally done in a systems language. I chose to do it in an applications language, and so far it is working out.

                    https://lobste.rs/s/4hx42h/assorted_thoughts_on_zig_rust#c_vytguc

                    http://www.oilshell.org/blog/2020/10/big-changes.html#appendix-the-tea-language (someone is actually helping me with this now!)

                    My thesis is that every piece of code that’s ~1M lines or even ~200K lines is almost inevitably 10x too slow. The best way to make code faster is to reduce the amount of it. Once you reach 100K or 1M lines, there’s very little you can do with a codebase. You can’t optimize code you don’t understand. Smaller applications are easier to understand.

                    There are exceptions, but they’re miracles. Linux and LLVM are close, in that they are 1M+ lines of systems code that are pretty damn fast, although they are probably 10x too slow in certain important use cases.

                    Systems code is just more verbose, and even a 20% tax adds up over the life of an application, but it can be more like 5x. Oil can reasonably claim a factor of 5x over the equivalent systems code: http://www.oilshell.org/blog/2019/06/17.html#why-is-it-written-in-python

                    And with no speed penalty so far: https://www.oilshell.org/blog/2020/01/parser-benchmarks.html

                    And certainly my impression of Rust code is that it’s quite wordy compared to say OCaml code, or Python code. Not sure about Zig. Metaprogramming should help to an extent.


                    The same distinction between systems and application languages is made here: https://without.boats/blog/revisiting-a-smaller-rust/

                    I think Go and Swift are nice offerings in this space… but ironically I would like an applications language that interfaces better with the OS (which are all written in C/C++). Go implements too much of the OS in its runtime. I haven’t really tried Swift, but AFAIK not many people use it on the server side.

                    1. 2

                      LLVM are close, in that they are 1M+ lines of systems code that are pretty damn fast

                      LLVM is reasonably fast for the quality of optimizations it does, it’s all well and good at scale…

                      but the lower bound on the time LLVM takes just kinda sucks, it’s bad for low latency applications – there are three high profile cases of projects hit by this:

                      • WebKit JavaScriptCore FTL JIT switched to a custom backend for the top tier because LLVM was not fast enough (wow, back in 2016! Four years ago already, damn)
                      • Mesa RADV (Radeon Vulkan) driver switched to a custom backend because LLVM was not fast enough, specifically for applications that constantly generate new shaders, like game console emulators (that don’t use an ubershader)
                      • Rust is adding a Cranelift backend for faster debug builds

                      Rust code is that it’s quite wordy

                      Depends on the abstractions you use and the tasks you want to perform. Rust starts getting really wordy as soon as you need to share access to objects. But straightforward data pipelines look very compact, “scripty” code that just does file open-write-whatever operations can be small too, same with using a high level framework like Rocket.

                      1. 2

                        Yup, that’s a good example of “being 10x too slow for one use case”, i.e. low latency compilers.

                        And my point is that I think you can essentially never change that about LLVM. It’s just too big to refactor in such a cross-cutting way. And that’s par for the course – of course it’s extraordinarily useful and amazing in other contexts.

                        The example from Linux would be containers, e.g. my rant a couple weeks ago:

                        https://lobste.rs/s/kj6vtn/it_s_time_say_goodbye_docker#c_nbe2co

                        Basically containers are hard in Linux because it’s a cross-cutting concern (it affects users, the file system, networking, process scheduing, etc.). The code is just too big to refactor to a clean design. It wasn’t built in, so it can never be added in a decent way.

                        So basically I think if you want software to last for awhile, you have to go to great lengths to keep it small and malleable at first. Oil’s code is 4.5 years old at this point, and it’s doing pretty good IMO. Of course every system runs into its limits, but I would like to put those limits off.


                        I don’t want to start a long conversation about this, but I’m puzzled by the claims of Rust being an application language. Zig has never claimed to be an application language as far as I can tell. For applications I believe you want to keep the code both small and nimble. Verbose code and long compile times are the opposite of that.

                        I have seen the comparisons of Python and Rust, and I’m not convinced. Although I would certainly choose Rust over raw C for applications, the main problem is the same one as in C: there is a tax on every single function (and that encourages long functions, among other things).

                        In fact I think big applications (e.g. with human users, GUIs, multimedia, and with nontrivial portability requirements) are a lot harder to write than Linux and LLVM. (And the most successful ones live just as long, like Photoshop or Audacity, etc.)

                        Kernels and compilers have well known algorithms and well known architectures. Applications are usually a much bigger heterogeneous mess. They have to deal with the quirks and special cases that users impose. GUI code seems to be irreducible in its “messy verbosity”, so I would like to move to higher level languages, not lower level ones. For all its problems, JS and React is a lot higher level than writing Win32 code or MFC code. Ditto with Elm, etc.

                        So basically I think the future of applications is higher level DSLs which hide detail, not systems languages which expose detail, and we’ve seen that in the last 10 years.

                        1. 1

                          basically I think the future of applications is higher level DSLs which hide detail, not systems languages which expose detail

                          And my point is that higher level DSLs can sometimes be built as embedded DSLs on top of systems languages. Why not? :) This is a relatively common genre of Rust project these days, e.g. there’s two competing ones for GTK GUI (one, two) at this point.

                          containers are hard in Linux because it’s a cross-cutting concern

                          ehh, I don’t think the API design decisions come directly from that. I suspect that NIH syndrome, an endless pursuit of flexibility, and not caring enough about security (especially back then) are all more relevant here.

                          A nice coherent API for handling many of these concerns existed all the way back in like 2000. It didn’t handle the “resource limits” concern, that was added later as a separate facility that integrates well with that one though.

                          1. 1

                            (late reply)

                            I’m not really experienced with desktops apps, so it’s hard for me to judge… I’ll just say I’ll be surprised if those approaches become dominant. It’s good that people are trying new things.

                            I know about jails and Solaris zones… I do think a big part of it is that FreeBSD is a significantly smaller program with less API surface. And they even have their user space utilities in the same source tree, and co-designed.

                            As mentioned, part of the problem with cgroups is that they were a kernel-only feature for a long time.

                            It’s sort of good for parallel / evolutionary experimental… That paradigm does produce some valuable new things. It also tends to produce really messy APIs.

                            And yes although Linux containers are super messy, they also had more functionality than Jails or Zones, i.e. the resource limits. That is typical of Linux. It has multiple ways to do everything and you can probably cobble together what you want with enough effort. But crosscutting concerns like security and performance are problematic.

                          2. 1

                            And my point is that I think you can essentially never change that about LLVM. It’s just too big to refactor in such a cross-cutting way. And that’s par for the course – of course it’s extraordinarily useful and amazing in other contexts.

                            There actually has been work on this in LLVM. It’s not been a particularly high priority yet, but it almost certainly will be as ORC matures. Most of the good ideas from FastISel are being incorporated into GlobalISel, so if you just want to emit fairly naive LLVM IR, not do (m)any optimisations, and generate native code, you will be able to use mostly the same code paths as the normal back ends.

                            Basically containers are hard in Linux because it’s a cross-cutting concern (it affects users, the file system, networking, process scheduing, etc.). The code is just too big to refactor to a clean design. It wasn’t built in, so it can never be added in a decent way.

                            I think that’s a political problem, not a technical one. FreeBSD was designed with the same abstractions as Linux, originally, as was Solaris, yet FreeBSD managed to retrofit Jails and Solaris managed Zones. The original Jails paper talked about adding macros to automatically turn a load of things that accessed global state into things that indirected via the jail state. There’s no reason Linux couldn’t build jails, though with a good kernel abstraction a lot of the value of something like containerd goes away and it’s harder to sell a Docker-like ecosystem as a value-added thing, so there’s less incentive for people to build these services on top of a good abstraction.

                            Kernels and compilers have well known algorithms and well known architectures

                            That hasn’t been true for the last 20 years. One of the problems with both domains is that the hardware has evolved very quickly and legacy implementations of the systems software have not been able to evolve at the same rate. Kernel bypass and enclaves / confidential VMs in the kernel side have dramatically changed the abstractions that kernels want to provide, with knock-on impact on how those are implemented. Compilers used to be the canonical sequential batch-processing applications and are now expected to be incremental, able to provide highlighting and error reporting in a few milliseconds, and massively parallel.

                            1. 1

                              (late reply)

                              One of the problems with both domains is that the hardware has evolved very quickly and legacy implementations of the systems software have not been able to evolve at the same rate.

                              Well yes that is basically my point. It’s hard to do anything architecturally with a program once it becomes too big. Changing application and hardware requirements put a strain on Linux’s architecture, and there’s a limit to what the solutions can be.

                              I think my claim about kernels and compilers is true relatively speaking – I was making a comparison to GUI applications.

                              With kernels and compilers, you can find architectural experiments and designs in academic papers going back decades. There is a “lag” but I think the knowledge does transfer / diffuse into practice.

                              There seems to be a wave of incremental compilers now, and they are all citing work from 10-20 years ago. You basically “invert” the compiler. The algorithms have been surveyed and documented, and they affect the entire compiler, i.e. it’s whole architecture.

                              And there is/was a lot of microkernel and kernel security research, and I believe at least some of it is pretty influential in Linux and other OSes.

                              Other knowledge/research that has actually made it into real kernels: http://www.watson.org/~robert/#publications (ha I see your name in many places here :) )

                              With applications, I see a lot less of that, although maybe I haven’t been following the right areas.


                              Actually I did a little project where I grepped for papers in both Linux and LLVM.

                              https://github.com/oilshell/blog-code/tree/master/grep-for-papers

                              I guess a lot of it was scheduling algorithms, but people tend to document algorithms rather than architecture. In any case I think there is more of a body of knowledge around kernel and compiler architecture than that of other programs.


                              Related claim about something that can’t be added once a program is too big: CPython has tried to add Lua/Tcl-like subinterpreters at many times, starting 15+ years ago. But security is a cross cutting concern (like performance, it doesn’t live in one place in the code), and it’s basically impossible.

                              https://lobste.rs/s/tjqmjy/re_when_will_browsers_be_complete#c_882lbz


                              Also, I do believe in the future that you will see kernels and compilers generated from high level specifications. It happens to a degree already, but there is still a lot of “chasing linked lists in C”.

                              What I call bash is “groveling through backslashes and braces one at a time in C”, and Oil is written in a completely different style than that.

                              Basically the point of codegen is that you can deal with cross-cutting concerns. Performance and security are the two main examples. They do not live at a specific place in the code; they are “emergent”, and depend on the architecture of the program, not specific lines of code.

                              So basically once you have 1M+ lines of code, there is little you can do to fundamentally alter performance or security. Well you can add faster APIs on the side, which is what Linux has done. Security is a lot harder, hence the container mess.

                              There’s no reason Linux couldn’t build jails, though with a good kernel abstraction a lot of the value of something like containerd goes away and it’s harder to sell a Docker-like ecosystem as a value-added thing, so there’s less incentive for people to build these services on top of a good abstraction.

                              addendum: It appears to me that the reason is lack of coordination and a heterogeneous codebase… that seems to be supported by the LWN link. That’s not entirely a technical reason, but non-technical factors can be the hardest ones, architecturally speaking…

                    2. 5

                      I remember being able to write some decent programs in Go within about two days. Julia, my current favorite was also somewhat similar. Learning Rust on the other hand felt a lot like learning Haskell. Simply a lot of concepts and theory to understand before you can do anything useful.

                      If C++ taught me anything, it is to value simplicity, and that is not something Rust does.

                      I strongly suspect someone is confusing simplicity and familiarity here. Granted, Rust isn’t the simplest language around. But I suspect one big reason why the author couldn’t be productive quickly was because Rust was different from what he was used to.

                      I don’t know Rust, never programmed a single line of code. But I strongly suspect my prior experience in OCaml will help me pick it up more quickly than many other programmers. It would feel simple. But that would just be familiarity.

                      1. 3

                        From my limited experience, I don’t think that familiarity is the main issue here. Rust’s borrow checker usually adds a lot of friction to that initial contact with the language. Once you’ve somewhat mastered it, there is the whole set of best practices, common patterns, and just “the way people should do stuff” that is spread among various crates. They’re not part of the language but to be an effective crustacean you kinda need to learn them and when to use them.

                        In my opinion, the journey of learning rust is trickier not because of the lack of familiarity but due to it introducing new concepts and the ways these new and old concepts interact with one another, and the ecosystem that is vast and unmapped for the newcomer.

                        1. 2

                          And I think my main gripe with Rust is that your suspicion is wrong. Rust is much more complex than Haskell. (Sorry, I don’t know OCaml)

                          Some example reasons: The types are more complicated:

                          • String vs str
                          • u64 vs i64 You have to constantly make sure you are doing the right conversions for these and in the right way. This often means littering your code with ?, try_from and similar shenanigans. The same holds for borrowing in general, str or &str, maybe even a lifetime annotation. Sometimes you need to add to_owned somewhere. Or introduce an additional temporary variable so that the thing you are borrowing from does not die. Something I can trivially write down the way you think it should work in Haskell takes me several minutes of tinkering in Rust. It’s surprising how complicated typical functional programming constructs become when the types don’t quite fit together.

                          Maybe there is no solution for this. Rust is just trying to give you access to every last bit of performance.

                          From an ergonomic point of view, I find Rust incredibly interesting. Haven’t we all at some point screwed up memory management in C and angrily proclaimed the compiler should have been able to figure this out? And then the guy next to you take a sip of his Club Mate and says “Yeah, but how would the compiler know the difference between X and Y?” And you go “We just do this and this and …” And at the end of a very long night of drinking and hacking you have Rust. It is still performant and it is absolutely wonderful how many errors you cannot make anymore.

                          Also, your dream language is now about as complicated and fun as C++. I hate it, but I am trying to get over that.

                          1. 2

                            String vs str

                            One is the actual owned string, another is a pointer+size referencing a part of it, like string_view in modern C++. Easy!

                            In Haskell, standard String is a linked list of chars, which nobody wants to use, so there’s Text and ByteString, multiply by two because each has a strict and lazy variant! Now we have five different string types, actually different (all “own” data – well it’s a GC language…)

                        2. 4

                          There’s a thing you can do to evaluate this. Take a source listing of the candinate replacement. Count up the files and lines in them. Then look up dependencies.

                          If there are dependencies, count their functionality into the system and add it to the line count. If the whole compiler + runtime is larger than 50k lines, then it will never replace C.

                          (Zip didn’t pass this test).

                          1. 12

                            Why? GCC has millions of lines of code.

                            1. 2

                              C spread across platforms by being simple to port. It was that because it’s not much. We’re talking about pre-GCC C here. By now GCC is so massive that vendors accommodate their platforms to fit it.

                              A language striving to replace C would need to have a pioneer’s structure as well. Otherwise it’s unable to skip the principal compiler on the platform and really replace it.

                              1. 7

                                The possibility of a small implementation of the language and the size of the main implementation aren’t necessarily related, though. Additionally, if you want to target a new platform nowadays, you’re better off adding an appropriate backend to GCC or LLVM instead of trying to implement C (or any other language) from scratch.

                                1. 2

                                  Of course, It’s better to not attempt to replace C. It works fairly well for what it was made for.

                                2. 2

                                  A lot of languages use LLVM as a back-end. This contradicts your thesis by adding a huge number of dependent LOC, but making porting really easy I.e. if there’s already an LLVM code generator for your platform, you’re mostly done.)

                                  And these days, hopefully any compiler segregates the code generation logic enough that it can be ported without worrying how large the front-end side of it is.

                                  1. 1

                                    LLVM itself has been written in C++, and that is an extension of C. That contradicts that none of this has replaced C yet?

                                    Honestly though I don’t believe it to be that important. I just don’t think that people move off C before the equivalent language can stand without C. There’s also a question of why to replace C? For example why would anybody want to write coreutils in a different language?

                                    1. 3

                                      For example why would anybody want to write coreutils in a different language?

                                      https://github.com/uutils/coreutils#why

                              2. 1

                                Note that there is ongoing work on a self-hosted compiler instead of leveraging LLVM.

                                1. 1

                                  That project seems to have been abandoned. The last commit was in November of 2019, and a month-old GitHub issue asking whether work has stopped has not been responded to by the developer.

                                  I was wondering what features Kit has other languages, including Zig, don’t have. Here are a few I found:

                                  • Automatic pointer referencing and dereferencing, as shown in these two examples
                                  • Term rewriting, whose use-cases can probably be served by Zig’s comptime feature
                                  • Implicits: in a constrained scope, Kit automatically passes the value declared as “implicit” to any function expecting that type of value. I think Zig would never want this feature due to Zig’s philosophy of explicitness and simplicity over convenience, as shown by Zig’s avoidance of operator overloading and exception-based control flow.
                                2. 1

                                  Excited to try this language at some point.