It's good to see the project soldier forward! Some things I am excited about for the future:
- Continued progress on the new "stage2" compiler, which is written in Zig itself (long promised, work started in earnest I think about six months ago). There will be an optional non-LLVM backend this time, which is good news for build times. Even in debug mode, most of build time currently is spent waiting for LLVM.
- Incremental compilation with live reloading (in-place binary patching). The stage2 compiler is being written with this feature from the start.
- I can't find the link, but I believe the plan for stage1 (the current compiler written in C++, with all its warts) is to simply delete it. Since the new compiler will also support compiling to C, the devs will simply have stage2 compile its own source into C at some point, and that will be the stage1 from that point forward.
So did I understand correctly? The "bootstrap" stage which is best to be in C (for the targets which don't have LLVM) will be just the output of the compiler compiling itself to C, and that will be regularly produced and pushed as the compiler grows? And that's one of the steps to make it LLVM independent?
P.S. Amazing work up to now! Kudos! I'm a fan just for everything it does what other language designers ignore, like designing the language to both compile and link fast. Cross compiling always available is a masterpiece. I guess that feature wouldn't have to be present on the platforms which don't have LLVM, if the language is to work on such too?
So this is purely a bug-fix release, with no changes to the language itself?
I hope Zig succeeds in becoming a stable and minimal language. Few languages manage it, they tend to steadily bloat over time. The most obvious exceptions are C and Scheme, which are famously ascetic.
Zig is a lot more complex than C, but I think most of the language complexity came from filling in gaps rather than tacking things on. For example I think they have settled on having no dynamic dispatch feature (interface, trait, virtual method, etc) at the language level. The comptime feature is flexible enough that userland approaches seem to be good enough. I bet a lot of "complexity" in the future will come from competing idioms used in various third party libraries.
> Zig is a lot more complex than C, but I think most of the language complexity came from filling in gaps rather than tacking things on.
I think it's important to keep in mind that with C you might end up using a bunch of extenions, complex pre-processor macros or pragma magic to achieve essential things.
C in itself is relatively simple, but using it in practice can end up becoming quite complex.
Zig ends up being simpler in some ways by having less magic and special ways of achieving things. "printf" is very magical in most C compilers, but the equivalent in Zig is nothing particularly special from either the language or the compilers side.
I think the only thing that truly adds complexity without being strictly necessary, is the async stuff. But you can argue that async IO is becoming a really essential thing for systems programming, and using it without explicit language suppport is a nightmare.
"printf" isn't magical, it's a standard va_args function. There's plenty of ugliness in the implementation [1] (so many macros!) but that's to be expected from something that has to simultaneously parse a format string and format output. The only magic is around format string checking.
Also the compiler is likely to optimise most calls to printf, transforming them into calls to other functions like puts [0], or apparently [1] even to fwrite. Probably doesn't count as magic, but almost.
having just learned it, async is not really complex, so much as "a thing that needs to be explained to you" kind of like how "pointers need to be explained to you". Sadly I don't see any good resources on that, yet.
Disagree, async (edit: in .Net at least) is pretty complex. For instance, from my experience it seems few C# programmers know that it's possible to deadlock if you get it wrong. [0][1] Plenty of people get confused with the basics, too. [2][3]
Zig is not .net. async in zig is simple, you just need to understand a few things: what a frame is, what the difference is between a stack frame and a heap frame, what it means (at the machine level) to jump between frames, and the fact that you can't do it in c. That's it!
It really is just a control flow structure in zig (which is why it's a keyword)
C is unusual though in that it's a minefield of undefined behaviour. It's very easy to think you truly understand the language but to have no real understanding of its many curious rules around undefined behaviour. You can't take a try it and see attitude, you need to be a language-lawyer.
Of course, even if you understand the rules you'll still accidentally write code that invokes undefined behaviour, which is much of the reason languages like Zig exist.
the difference is, they try to check for them at compile time, and if you compile with safety checks, also at runtime; however, if you compile with ReleaseFast (which I assume most people will in production), those runtime checks are turned off and the undefined behavior still exists.
Having your account hacked is a disastrous consequence though. And it's likely that the need of performance is not on the CPU side of things but on the GPU so ReleaseSafe should be good enough.
Right, I hadn't meant to imply Zig is free of UB. It aims to improve on C's wild-west UB rules not by having no UB, but by having only a manageable dose of it, and supporting good optional runtime checks.
Zig's approach is essentially that of Ada. You can ask the Ada compiler for runtime checks, or promise the compiler that your code is free of undefined behaviour and have your code run at the speed of light (C), or go haywire if you got it wrong. Sadly C is less suited to runtime checks, arrays are dealt with through raw pointers so range checks aren't easy for the compiler to add automatically. You can though ask GCC to generate checks (trap-on-failure) for things like dereferencing NULL, or signed arithmetic overflow.
The most convenient way is to probably to use a compiler builtin https://gcc.gnu.org/onlinedocs/gcc/Integer-Overflow-Builtins..., if you want to be portable the next easiest way is to use a wide enough type (e.g. add or multiply two 32 bit numbers to a 64 bit one and verify it is inside [INT_MIN, INT_MAX]). Otherwise, you can either do a pre-condition check (for addition overflow occurs if a > 0 && b > 0 && a > INT_MAX-b || a < 0 && b < 0 && a < INT_MIN-b) or work with unsigned integers and check for wraparound after the operation. Finally, both clang and gcc have options to check for signed integer overflow at runtime (-fsanitize=signed-integer-overflow for gcc).
Of course, in practice this is too much effort for most people most of the time, so actual deployed C and C++ code is full of undefined behavior due to integer overflow. This paper has a great overview:
The overflow happens in the computation, you don't typically determine whether there was an overflow by checking the variable into which the result is saved (although perhaps this approach could work if you're using an unsigned integer type, which wraps around on overflow). You'd normally perform the check before you perform the addition (or whichever operation risks overflow). This can be fiddly to get right, but it's possible.
Also, the maximum value that can be represented in a variable of type uint32_t is (2^32) - 1, not 2^32.
Chromium, OpenSSL, the Linux kernel, the Windows NT kernel, have all suffered from security vulnerabilities due to undefined behaviour. We can bet they will continue to suffer from such issues. It's not something you can avoid simply by being competent and careful.
edit: As lmm says, it's likely you have UB issues in your code you aren't aware of. That's not quite the same thing as having issues in your code due to not being a good enough language-lawyer. I've resolved some very subtle issues that found their ways into a 'serious' C++ codebase, and I didn't spend that long in the C++ world. In most languages those issues simply couldn't have happened in the first place.
> the more bugchecked and field tested the code is, the more obscure any bug that surface is?
Right. A battle-tested codebase only has subtle errors, as the obvious ones will all have been fixed. An immature codebase has subtle errors and more obvious ones.
> GCC compiles to alot of architectures. I have a hard time imagining any modern language compiling to all those platforms without quirks in practice.
Compiler bugs are a separate issue from undefined behaviour and surprising language subtleties. With mature compilers they're pretty rare, but they do happen.
JavaScript is a good example. There's no undefined behaviour in JavaScript. That's vitally important given that JavaScript engines have to be able to run untrusted code. If JavaScript code is able to cause undefined behaviour, that's a serious security issue in the engine. Such bugs do happen, of course, but they aren't all that common. Generally, JavaScript runs fine regardless of whether you're running on x86, AMD64, or AArch64. Same goes for Java.
(I admit I'm ignoring the possibility of a constrained/contained kind of undefined behaviour where the JavaScript context might see things go haywire but the process containing the JavaScript environment is unaffected.)
How do you know? One of the reasons they're so insidious is that code that hits them tends to work fine until it gets compiled with a newer version of the compiler. E.g. signed integer overflow did exactly what you expect in most compilers until fairly recently.
> E.g. signed integer overflow did exactly what you expect in most compilers until fairly recently.
How recently? Both gcc 4.1.2 (2007) and clang 3.0.0 (2011) optimizes `x+1 > x` to true for a signed int `x` on -O1. And it probably goes way back, these are just the oldest compilers I found on godbolt.
Ah, point taken, but that's within the bounds of what many people expect; propagating the fact that the overflow is "impossible" to rearrange earlier control flow is more surprising and more recent.
> Ah, point taken, but that's within the bounds of what many people expect;
The thing is it's very hard to draw the line once you go that route. Different people expect different things from undefined behavior. The best thing is to not expect anything sane. And if you are unhappy about certain undefined behaviors in the standard then it's better to push the standard to define more behavior. Certain unnecessary undefined behaviors get resolved with newer standards, although I would expect significant pushback on defining the behavior signed integer overflow.
I understand there's a good chance the next standard will specify that signed integer overflow results in an unspecified value, which would match the behaviour of older compilers and what (IME) most programmers tend to expect.
Ye that is true. C compilers got some strange gotchas that you need to memorize but my main point is that those problems atleast to my projects are miniscule compared to off by one out of bound array access or dereferencing null pointers.
I agree with you here, but even these two categories of runtime errors are much more painful in C/C++ than in most other languages.
As I mentioned elsewhere in the thread, you can ask gcc to trap if your code is about to dereference NULL, but the compiler can't easily detect all instances of out-of-bounds array access, due to the way arrays and pointers work in C. I believe Valgrind can help detect out-of-bounds errors at runtime, but in most languages you don't need a sledgehammer like Valgrind to find these common errors.
We're agreed. I use it for my personal C++ projects. It's really quite awful (I've ranted about it on HN more than once [0][1]) but it's still the least bad choice.
I don't think there's that much harm in compilers offering non-standard extensions. A lot of programmers will stick the standard language, so the drawbacks aren't the same.
As well as the gazillion languages that compile to and interoperate well with C, such as Nim and several Scheme dialects (Chez and Chicken, probably others I'm forgetting). Oh and Haxe, which apparently can compile to like 10 different languages and I don't understand why it isn't more popular.
I like all of these languages because they don't require the user to switch to a different library ecosystem in order to use a programming language that they find more comfortable/enjoyable/powerful/expressive. I consider this extremely important because of how complicated some low-level protocols and systems can be, and reimplementing everything again for every new language is a waste of effort in my opinion.
Also, the proliferation of things that specifically compile to JS enforces a only-half-joking saying I heard a long time ago: JavaScript is a better compiler target than programming language. I think I've heard the same said about C as well.
With respect, although I think zig is very fascinating as far as language design and is great work, I've had trouble compiling most example zig programs, which I assume worked on older versions of zig. This minimizes my motivation to continue to learn zig.
Please accept pull requests that fix older example code, or please make the provided examples work on newer versions of zig.
You left that PR open for 7 days before closing it?
Average PR lifetime in ziglang/zig repo itself is about 12 days. This is actually pretty fast compared to other projects. I'm proud of the fact that the oldest open PR in zig is from October 2, 2020 - only 2 months and 11 days old.
From the perspective of someone submitting a pull request, it's frustrating to not get a response within a day or two. I completely get that. But it's just not possible when you have a team of 1-3 people who are able to engage with PRs, and hundreds of people submitting PRs.
Since this project has started, the Earth has completed 1732 rotations, and yet 2877 pull requests have been closed. That's 1.7 per day, including weekends and holidays. Consider that for the first couple of years of this project, I was moonlighting it with a day job.
I'm not trying to throw shade at you, just saying, man, cut me some slack. I'll get to your PR.
This is a bug fix release only - only bug fixes have been pulled into the 0.7.x branch. The release is cut when all the issues with the 0.7.1 milestone are fixed. The main branch has new features already that are not in this release. They will be available with 0.8.0.
In the general sense, no, not yet, of course, but in one sense, Zig is probably more production ready than Rust, for example on embedded systems with obscure architectures where OOM-safety is essential.
Here is a talk on using Zig in exactly this kind of environment in production:
> In the general sense, no, not yet, of course, but in one sense, Zig is probably more production ready than Rust, for example on embedded systems with obscure architectures where OOM-safety is essential.
I mean, FWIW, Rust is fairly ready for embedded systems, even when OOM-safety is essential. There are crates dedicated to providing fallible memory allocation primitives for exactly those use-cases.
With that said: I do believe Zig would work a lot better than Rust in severely resource-constrained environments. Rust has a few pitfalls that can easily cause a code size explosion (anything using core::fmt, for instance). And zig's constexpr appear more powerful than Rust's current stable const fn, which can be critical to writing maintainable, small code.
I've worked on a Rust reimplementation of a bootloader that had to fit in 8KiB of RAM, and I often went over accidentally by having a panic handler pull core::fmt. I haven't tried Zig for the same use-case (yet), but from a cursory glance, it appears to be less prone to this kind of code-size explosions.
It's another "C but better" language. Contrasted with some of the other solutions in that space, it has an emphasis on stability (once 1.0 is released) and simplicity -- eliminating low hanging fruit in terms of undefined behavior and whatnot, but not significantly hampering expressiveness or requiring excessive syntactic contortions to go the last mile and make it completely safe.
This is actually a surprisingly underserved field. There's relly very few language that actually tries to be just C but better.
Go: Uses garbage collection, not very applicable
Rust: Too complex, more like C++ but better. Bit too cumbersome to write "unsafe" code
D: More like Java/C++, although they have a "better C" mode, but feels like an afterthought
Nim: Uses garbage collection.. like D they're kind of trying to reach down into the plain C usecase, now with some nice reference counting stuff. Also relatively complex
Some actual attemps that I know of: C2, Odin
Some reasons why Zig is a better attempt at "C but better":
- Complexity of the language is just a little higher than C, and mostly in ways that resolves issues with C that leads to its own complexities (Like, the C pre-processor is simple, but solving problems with it can be surpringly complex. Or how you may have to resort to compiler extensions/pragmas to solve real problems)
- The Zig compiler is also a fantastic C compiler
- C headers can be imported and used directly in many cases
- Significant parts of the language is dedicated to excellent interop with C (even has syntax for 0-terminated strings)
- Cross-compilation is a highly prioritized use-case (C is very popular in embedded programming, where cross-compilation is essential)
I don't think it's fair to dismiss some of those, like Rust. That project very explicitly wants to be viable as a safe tool for writing systems code, and the fact that it's accumulated a lot of complexity doesn't take away that intent (or the fact that it's being successfully used in that capacity).
That said, I am super excited about the Zig project, and it hits a sweet spot for me in a way that none of the other "C but better" projects have. Calling it another "C but better" language wasn't an attempt to disparage the project, but to classify it.
> That project very explicitly wants to be viable as a safe tool for writing systems code
Right, but that's a (slightly) different goal from being a C replacement. There are other systems languages besides C, and C is used beyond systems programming.
I guess we're disagreeing about the semantics of "C but better" then. Your position seems to be that a contender would need to be able to replace (and improve) C generally, and I don't totally agree (not that you need to be convinced, but I'll elaborate on my interpretation anyway).
E.g., if somebody came along and said that C would be bloody perfect if it just had a garbage collector then that alternative language would be totally unsuitable as a replacement for C in many contexts, but for a ton of other scenarios it'd still be "C but better," and I don't know that it should be disqualified just because it isn't a full replacement.
Ada is often overlooked. Very short summary: It's like C in that it's plain imperative code that compiles down to efficient machine-code pretty naturally. It's unlike C in that it's much safer, with excellent support for strong optional runtime checks. Ada has excellent interoperability with C. Syntactically it's pretty different from C.
You should try zig. You'll never want to go back, and the c interop is so good, it's easy to kill the c code over time using embrace extend extinguish.
As someone who loves C, Zig has replaced C for me and I don't plan to go back.
There are just too many benefits, not least developer velocity and readability.
Zig is also very approachable to anyone who is familiar with TypeScript, in a way that C isn't. This means more of your team can get excited and get involved, at least in terms of reading the code.
And with Zig, where you have to spend a little extra maintenance time paying for the language being pre 1.0, that's still nothing compared to the upfront and long-lived costs associated with C. You can develop so much faster in Zig compared to C, that you're ahead even pre 1.0.
To expand on this a bit: I feel like learning Zig has helped me better understand C, particularly when it comes to allocation and pointers. Zig has more facilities to specify the intent behind a lot of the common C programming concepts, which has helped make things "click" in both languages.
I’m looking for a new systems programming language. For all the hype of Rust, I don’t think this is it.
How does Zig compare to Nim?
Does Zig have a future in games programming? I feel games programming is the ultimate test, to validate the true success of a programming language.
I do like that Zig compiles to LLVM. Although this in itself will exact a minor performance penalty, in exchange for wider CPU coverage.
Nim transpiles its code down to C, and I’m not certain how that will play out. As you’re now writing your new language on an already somewhat buggy language.
But this does give Nim the possible speed advantage, of getting the code compiled closer to the metal, whereas Zig will compile to the intermedia LLVM layer.
It's going to start compiling to zig IR soon (if it doesn't already), and zig IR will be translated to LLVM IR or fed into zigc. So I think the recommended development cycle will be - use zigc for fast iteration on localdev, then when releasing, use slower LLVM for extra optimization passes and also for cross-compilation.
> gamedev
I think the highest interest in zig is currently gamedev, but there are SO MANY uses for zig.
C doesn't have to die. The point was just that many places where C is currently a good choice you'll find that Zig is also a good choice, and if you like the "but better" features it brings to the table then you'll be inclined to choose Zig. As another comment pointed out, you'll have minimal regret when doing so because the C interop is excellent.
It's a general-purpose programming language, but its primary use case is low-level programming. Anything you'd use C or C++ for today -- OS kernels, command-line applications, games, drivers, embedded software, signal processing, packet filtering, VMs, and applications that require precise control over resources. Zig brings a radical new approach to this niche that reconsiders how low-level programming should be done -- unlike, say, Rust which is an improved C++ -- and is one of the most interesting languages I've seen in a very long time.
Similar to C but less footguns, less error-prone error handling, better meta-programming, really good C interop. If you're writing C (or maybe even C++) on a platform that llvm supports, it's worth checking out.
You have to spawn a thread using a method of your choice. Once you've done thar though zig gives you some very nice async primitives that let you reenter functions and do scheduling/event loops easily, and it's agnostic; the calls autoconvert to blocking if you don't have async. There was a recent demo of task sharing in zig and comparing to go and rust: (https://youtu.be/UaF6-5BmX2I&t=1h10m)
I've even plugged zig into another highly concurrent vm and gotten it to be a "good citizen": https://youtu.be/l848TOmI6LI (these constructs are running concurrently in test and CI so I know they are robust)
I know that as a language author it's natural to assert "it can be used for (almost) everything", but it's not a good strategy for adoption - plenty of languages can claim the same after all.
It's better to mention in which areas it really excels, where it beats the competition hands down.
* Things that would be in scope, but the Zig project is not yet capable of supporting. An example of this would be compiling stuff to GPUs, e.g. SPIR-V. Such things are planned but not implemented. https://github.com/ziglang/zig/issues/2683
* Things that will never be in scope. An example of this would be code obfuscation, which is a use case the Zig project does not recognize.
* Object formats that are drastically incompatible with the von Neumann architecture. It would be infeasible for Zig code to be compiled to such things. For example, attempting to compile to CSS (Cascading Style Sheets).
I think that's it. Anything else would be supported, provided there were enough people interested in providing maintenance for the respective parts of the codebase.
writing code for gpus, writing "code" for FPGAs/ASICs, writing excel macros, writing MAX presentations... Probably most websites (not counting any WASM layers...)
If I like (for whatever reasons) to primarily program in a classical OOP style with objects that have encapsulated state and can be late-bound then at the moment it looks like that this would not be a great use case for Zig. Would this be true?
(BTW I really like Zig's philosophy in general, just saying that this might not be a great use case for it)
- Continued progress on the new "stage2" compiler, which is written in Zig itself (long promised, work started in earnest I think about six months ago). There will be an optional non-LLVM backend this time, which is good news for build times. Even in debug mode, most of build time currently is spent waiting for LLVM.
- Incremental compilation with live reloading (in-place binary patching). The stage2 compiler is being written with this feature from the start.
- I can't find the link, but I believe the plan for stage1 (the current compiler written in C++, with all its warts) is to simply delete it. Since the new compiler will also support compiling to C, the devs will simply have stage2 compile its own source into C at some point, and that will be the stage1 from that point forward.