37 comments

[ 4.2 ms ] story [ 92.2 ms ] thread
This is great, visibility makes the zig developers accountable and gives a clear incentive not to regress. Does anyone know of similar projects from other languages?
(comment deleted)
Arguably, zig is not yet at the point where they should care very much about performance regressions.
Zig is very much a performance language, so it would make sense to track that metric from the get go (haha).
They should care, though they probably have more leeway to drop performance for the right trade off since they are not stable yet.
Zig is growing fast, and the dev team should feel proud of what they're building. I think it's great to see metrics like this because they demonstrate an attention to detail that will be necessary for Zig's success.

Metrics help ensure that Zig will stay high-performance as it moves to its self-hosted compiler, which is underway right now.

excellent compilation speed is one of the main goals of the self-hosted compiler, which is what is being developed right now, so it makes total sense to track related metrics
Nice plots, they seem to be svg and link directly to the appropriate github commits if to click on a particular point. I wonder if they were coded by hand or if a plotting library was involved.
Can someone give me an elevator-pitch for Zig? When would I use it? Is it worth checking out in 2021?
> Can someone give me an elevator-pitch for Zig?

The main page ziglang.com makes a good job at that:

  A simple language:
    * No hidden control flow.
    * No hidden memory allocations.
    * No preprocessor, no macros.

  Compile time goodness:
    * Call any function at compile-time.
    * Manipulate types as values without runtime overhead.
    * Comptime emulates the target architecture.

  Close to C and C++:
    * Use Zig as a zero-dependency, drop-in C/C++ compiler
      that supports cross-compilation out-of-the-box.
    * Add a Zig compilation unit to C/C++ projects;
      cross-language LTO is enabled by default.
comptime is hard to grasp from that blurb. for c programmers, here's a good example:

you know how gcc will usually optimize `memcpy(dest, src, strlen("hello"))` to just `memcpy(dest, src, 5)` -- because it knows how strlen works, and in this case all the inputs are known at compile time?

well, in zig, if a function is pure and all of its inputs are known at compile time, the compiler will always run the function at compile time and replace the function call with the result. you can also add the keyword `comptime` to force this behavior on a per-statement basis.

so comptime gives you conditional compilation and also results in more optimized code. coupled with zig's import system, this eliminates the need for a preprocessor. perhaps more interestingly: even types can be comptimed... which gives you generics 'for free'.

edit: removed inaccurate assumption regarding qemu

> it actually builds a stub program and runs it in qemu for your target arch

That's not at all how the implementation works, the zig compiler does not depend on qemu. The comptime evaluation works much more like evaluating an interpreted language such as python.

To provide a simple example of how comptime is used for generics in Zig, consider the ArrayList. Idiomatic Zig code uses TitleCase for types, but ArrayList is technically a function with the signature `ArrayList(comptime T: type) type`. Given the type `T` as an argument, it returns a new type `ArrayList(T)` that implements the ArrayList for `T`. You can initialize a new ArrayList of `Foo`s like this: `var list = ArrayList(Foo).init(allocator);`.

No second macro language, no magic variables, no special syntax.

Zig is a general purpose language with no hidden control flow, powerful compile time metaprogramming, and easy cross-compilation and interop with C code.

For me it hits the sweet spot of fixing major problems in C, while staying small, consistent, and easy to fit the language inside my head unlike C++ or Rust. Also absolutely loving the compile time evaluation and type syntax.

Interesting. I keep seeing marketing on “no hidden flow control”, but I’m curious what flow control is being hidden in other languages? Are they talking about exception handling?
Operator overloading, constructors, destructors, exceptions, etc. This page [0] is good for comparing Zig to other languages. And this [1] is a nice overview of the language.

One reason I think "no hidden control flow" is used so much when talking about Zig, is because Zig wants code to be easy to read, including understanding what is happening behind the scenes. Zig also has no hidden allocations (a function call will not allocate unless given an allocator) which fits this model.

[0]: https://ziglang.org/learn/why_zig_rust_d_cpp/ [1]: https://ziglang.org/learn/overview/

A Better C. But It doesn't provide memory safety like Rust.
(comment deleted)
I discovered Zig a few days ago. I'm evaluating it for use with my STM32 microcontroller.

A few observations: * it cross-compiles to other architectures without any extra libraries required. The chances are that you've already installed the GNU cross-compiler tools, but it's a nice touch to have an all-in-one solution. * you can split memory into bitfields. This is useful for microcontrollers, where different parts of a 32-bit register do different things. The compiler can check the field widths, too. C++ does have bitfields, but AFAIK, it doesn't give guarantees as to exactly how the bits might be packed. * I can have a single registers file, that lays out the memory map of the microcontroller, somewhere in my path. In C, vendor-supplied libraries tend to have definitions all over the place, so a single file is a bonus. * a module system. You write the build script in Zig itself. * no macros, but it can perform compile-time execution. I've been wondering if compile-time execution actually provides a whole new way of thinking about programming. * async/await/suspend/resume. Ooo, a way of doing cooperative programming baked into the language itself. Seems very handy for microcontrollers * a library system that is part of the language. This means that you could write bare-metal systems, and it will give you things like maps and list. You don't have to try to integrate third-party libraries like newlib. Presumably it doesn't all work out of the box, though. I've not delved into the mechanics of it yet.

There are plenty of other things of well, but those are the ones that jumped out at me if you're interested in system-level work.

Zig is squarely aimed at C, a kind of "C done right". The syntax is fairly intuitive to understand. In terms of size, there's virtually a one-to-one translation between C and Zig. Zig aims for correctness and transparency, although it isn't as strict as Rust.

To give perhaps one example, C99 introduced flexible array members, so you could do stuff like

``` void func(int n) { int arr[n]; } ```

And the question is: where/how is the memory allocated for arr? The answer is: probably on the heap. If you're using a microcontroller, though, heap allocation might be a big no-no. You need a memory allocator for starters, and you have to worry about memory fragmentation on resource-constrained systems.

Zig takes the philosophy, if I can put words in the developers mouths, of having a strong dislike of "stuff going on under the hood" that you don't know about. They take and ability to reason about what the code does seriously.

Isn't your example code a stack allocated array? Flexible array members would rather be:

  struct Foo {
    int len;
    int data[];
  } *foo = malloc(sizeof(struct Foo) + n*sizeof(foo->data[0]));
  foo->len=n;
Zig is a really exciting language to see grow.

As a minor correction to your post: C99's flexible arrays are stack-allocated, not heap-allocated. Since stacks are often small, this feature actually wound up causing a bunch of designers to hit stack overflows. It was downgraded to an optional extension in C11.

Zig makes it easy to write correct, reusable, low-level code.

Correctness: Zig (compared to C) introduced defer, errdefer, optional types, exhaustive switch statements, comptime, error sets, .... These have comparable succinctness to how error handling, null checking, macros and whatnot would be done in C, but they allow the compiler to check your work. Moreover, you gain some small optimizations because the compiler can infer intent a bit better (e.g., eliding null-checks on non-optional pointers).

Reusable: Zig operates on a principle of least power. If you don't need libc you don't link it. If you don't need an OS you don't depend on syscalls. If you don't need to use a specific memory allocator you pass one in as an argument. If you don't need a stack you don't set one up. Because of this, nearly the entire standard library can be used in UEFI apps, WASM modules, embedded unikernels, and all kinds of places where you'd be fighting against a typical C/C++/whatever toolchain.

Reusable: Comptime simplifies a lot of parts of the language design. It allows for easy generics (e.g., writing a maximum(a,b) macro in C that behaves correctly requires a ton of boilerplate or compiler-specific preprocessor extensions that don't even exist in any form in every major C compiler). It also allows you to write things like lookup tables and embedded data in Zig itself, rather than copy/paste/fat-fingering from another tool as part of your build process. Then those functions that generate your tables are testable and can be reused elsewhere.

Low-level: Much like C you have nearly complete control of the machine. You can easily override the _start symbol, drop into assembly, edit your favorite memory directly, set up dual stacks to trampoline your function calls against, call into any C library, present a C interface to Python, etc. Some low-level tasks like register bit-banging are way easier because the packed struct interface allows you to manipulate packed fields directly rather than shifting and masking your way to the right answer.

I'd reach for Zig any time I would have formerly reached for C, especially in any kind of atypical (embedded, no libc, no filesystem, ...) scenarios. I personally also don't find the high-level features of C++ and whatnot worth it compared to their downsides most of the time, so if I don't want something like Python/Perl/Javascript then I'm usually inclined to drop down as far as C (now Zig) anyway unless I have a good reason to do otherwise (like using the EGG library in Rust).

Is it worth checking out in 2021? Well, it hasn't hit 1.0, still has bugs, and isn't perfect. I'd be careful which production systems I ran on it. That said, I've had a blast using Zig for FUSE programs, Sudoku solvers, and all kinds of things. Give it a shot.

Does Zig support SIMD now?
SIMD support is almost complete. The documentation is still lacking, and some functions are still missing.
(comment deleted)
I remember Vlang[0], I wonder how they are doing and if it's comparable to Zig, in terms of performance.

[0]: https://github.com/vlang/v

they delivered on their promises

meanwhile zig still isn't self hosted

both projects are interesting, i feel the later gets way too much credits compared to V

Zig seems more complex than V due to its "compile time" execution, so I'm not surprised that it takes longer to write a "self-hosted" compiler.

V seems more general purpose, Zig is focused on embedded which makes it more difficult to use IMHO: any V function can allocate but in Zig you have to pass around the allocator..

Zig is general purpose and isn't really focused on embedded as much as it's focused on being flexible enough to also support embedded development.

Configurable allocators are present in other languages (iirc rust allows it at the crate level, in C++ https://en.cppreference.com/w/cpp/memory/allocator) but in zig the style makes allocating functions explicit which is nice even for regular application development. It's a bit like Haskell being explicit about side-effects (IO Monad, Eff, etc). Also, passing the allocator in zig is just a convention and not enforced by the compiler so you could use `std.heap.page_allocator` or any other global allocator anywhere in your code without passing it in but then your code is less flexible.

Now for the use of the, imagine you need to serve a HTTP request; you pass an ArenaAllocator to the request handler which can allocate memory by need and without a need for calling free() for small objects. Once the handler has generated a response, the caller can free up all of the allocated memory in one step rather than having small deallocations all over for memory that will only have a life-time for the duration of the request. The handler itself doesn't need to know that it's being used this way and can be written to call free in the usual manner to be compatible with other allocators (free() with ArenaAllocator is a "nop").

Imagine you have a game, there you'll likely allocate a huge chunk at the start for all of your resources then use your own custom allocators within to both isolate systems from one another along with allowing different strategies based on your usecase. You might not always know which compiler will be best but since it's a parameter you can easily test or even decide at runtime on the strategy to use.