ZIG programming language.

The problem with Go <-> C interoperability is that calling C from Go has high overhead. Although there is some guy working on making things better, I guess.
 
I hear this a lot. But looking through the docs, I just don't see how all that boilerplate could be easier:

https://ziglearn.org/chapter-4/

For example this wrapping example may look easier than i.e Java JNI but it is still a massive waste of time when you could just use C or C++ and avoid this time consuming, error prone step entirely. I must be missing something because Zig does embed a C compiler in it, it just doesn't seem to use it well?
It is explained on their official page: https://ziglang.org/learn/overview/#integration-with-c-libraries-without-ffibindings
This Zig code is significantly simpler than the equivalent C code, as well as having more safety protections, and all this is accomplished by directly importing the C header file - no API bindings.
Zig is better at using C libraries than C is at using C libraries.


Zig can integrate with C libraries without the use of FFI/bindings.

Zig does something quite special that puts it ahead of the crowd in this space; It can import and use C libraries as easily as C does (no bindings required), and it can itself be built into a C library, auto-generating the required C headers.

The improvements in language simplicity relate to flow control, function calls, library imports, variable declaration and Unicode support.

Zig promotes an evolutionary approach to using the language that combines new Zig code with existing C code. To do this, it aims to make interaction with existing C libraries as seamless as possible. Zig imports its own libraries with the @import directive, typically in this fashion:

const std = @import("std");
Zig code within that file can now call functions inside std, for instance:

std.debug.print("Hello, world!\n", .{});
To work with C code, one simply replaces the @import with @cImport:

const c = @cImport(@cInclude("soundio/soundio.h"));
The Zig code can now call functions in the soundio library as if they were native Zig code.
 
It is explained on their official page: https://ziglang.org/learn/overview/#integration-with-c-libraries-without-ffibindings
This Zig code is significantly simpler than the equivalent C code, as well as having more safety protections, and all this is accomplished by directly importing the C header file - no API bindings.
Zig is better at using C libraries than C is at using C libraries.
So on that very page, you have some "binding glue" function such as this:

Code:
fn sio_err(err: c_int) !void {
    switch (err) {
        c.SoundIoErrorNone => {},
        c.SoundIoErrorNoMem => return error.NoMem,
        c.SoundIoErrorInitAudioBackend => return error.InitAudioBackend,
        c.SoundIoErrorSystemResources => return error.SystemResources,
        c.SoundIoErrorOpeningDevice => return error.OpeningDevice,
        c.SoundIoErrorNoSuchDevice => return error.NoSuchDevice,
        c.SoundIoErrorInvalid => return error.Invalid,
        c.SoundIoErrorBackendUnavailable => return error.BackendUnavailable,
        c.SoundIoErrorStreaming => return error.Streaming,
        c.SoundIoErrorIncompatibleDevice => return error.IncompatibleDevice,
        c.SoundIoErrorNoSuchClient => return error.NoSuchClient,
        c.SoundIoErrorIncompatibleBackend => return error.IncompatibleBackend,
        c.SoundIoErrorBackendDisconnected => return error.BackendDisconnected,
        c.SoundIoErrorInterrupted => return error.Interrupted,
        c.SoundIoErrorUnderflow => return error.Underflow,
        c.SoundIoErrorEncodingString => return error.EncodingString,
        else => return error.Unknown,
    }
}

This maps the different errors provided by the C API to the idiomatic Zig counterparts.

The problem is, this is fragile to maintain. In a typical audio program you might only specifically reference one or two of these. Whereas in Zig, having to create glue code for each and every one means that if anything gets *removed* or *renamed* in the C API, it will break the Zig bindings and fail to compile ("c.SoundIoErrorIncompatibleBackend undefined"). If the upstream API *adds* a new error type, your bindings will miss it and you will have some unhandled error.

Other non-C languages "solve" this by providing a language based package manager to juggle all the naff bindings. Zig bindings look lighter but honestly I still would not want to write or maintain that boilerplate.
 
So on that very page, you have some "binding glue" function such as this:

Code:
fn sio_err(err: c_int) !void {
    switch (err) {
        c.SoundIoErrorNone => {},
        c.SoundIoErrorNoMem => return error.NoMem,
        c.SoundIoErrorInitAudioBackend => return error.InitAudioBackend,
        c.SoundIoErrorSystemResources => return error.SystemResources,
        c.SoundIoErrorOpeningDevice => return error.OpeningDevice,
        c.SoundIoErrorNoSuchDevice => return error.NoSuchDevice,
        c.SoundIoErrorInvalid => return error.Invalid,
        c.SoundIoErrorBackendUnavailable => return error.BackendUnavailable,
        c.SoundIoErrorStreaming => return error.Streaming,
        c.SoundIoErrorIncompatibleDevice => return error.IncompatibleDevice,
        c.SoundIoErrorNoSuchClient => return error.NoSuchClient,
        c.SoundIoErrorIncompatibleBackend => return error.IncompatibleBackend,
        c.SoundIoErrorBackendDisconnected => return error.BackendDisconnected,
        c.SoundIoErrorInterrupted => return error.Interrupted,
        c.SoundIoErrorUnderflow => return error.Underflow,
        c.SoundIoErrorEncodingString => return error.EncodingString,
        else => return error.Unknown,
    }
}

This maps the different errors provided by the C API to the idiomatic Zig counterparts.

The problem is, this is fragile to maintain. In a typical audio program you might only specifically reference one or two of these. Whereas in Zig, having to create glue code for each and every one means that if anything gets *removed* or *renamed* in the C API, it will break the Zig bindings and fail to compile ("c.SoundIoErrorIncompatibleBackend undefined"). If the upstream API *adds* a new error type, your bindings will miss it and you will have some unhandled error.

Other non-C languages "solve" this by providing a language based package manager to juggle all the naff bindings. Zig bindings look lighter but honestly I still would not want to write or maintain that boilerplate.
I only know 'shell scripting' and 'REBOL', and I have very limited knowledge of a few other languages. You should discuss this with 'Zig programmers' rather than me.

However, I can say how it seems to me.
The text you quote is written under the following sentence:
@cImport directly imports types, variables, functions, and simple macros for use in Zig. It even translates inline functions from C into Zig.
It seems that you are just talking about such a function that has been automatically translated from C to Zig.
You have the sentence 'This Zig code is significantly simpler than the equivalent C code' and this text is a link. If you click on the link you can see what they mean.

Coming from an ASM background, I have great respect for optimizing compilers.
In the OS, every cycle (should) count.
jai would be much faster at this than the D language.
D, in turn, is quite a bit faster than all the rest.
I haven't tested the Red language yet but it should also have fairly fast compilation times.
Red is also one of the most compact programming languages with an average of 2.4 times less LOC per commit for program maintenance compared to Python and Ruby.
It is a language that is much more productive in writing an app, but also requires much less work later on for the maintenance of the software. And it compiles fast.
 
You have the sentence 'This Zig code is significantly simpler than the equivalent C code' and this text is a link. If you click on the link you can see what they mean.
In that example given I believe they are almost being deliberately misleading.

Typically in any language, if something goes wrong you want to clean up. However, if something goes right, you obviously *don't* want to clean up what you have just succeeded in creating.

So their limited example (with a while loop at the bottom to prevent the earlier allocations from being stripped under you when the function ends) demonstrates their "defer" mechanism. However they evidently have no way of saying, "only clean up the memory if the rest of the function is not successful".

Personally in this case, I would go for 2-goto.c. It is the cleaner design. And unlike Zig (it seems), it would be possible to nicely split up the code into an Initialize() and Run() function.

But I could be missing something. Perhaps the lifetime of the "deferred" object is tracked. Not sure how this would be possible for primitives without overhead or how this works with owning/non-owning observers. C is so much simpler = safety.
 
In that example given I believe they are almost being deliberately misleading.

Typically in any language, if something goes wrong you want to clean up. However, if something goes right, you obviously *don't* want to clean up what you have just succeeded in creating.

So their limited example (with a while loop at the bottom to prevent the earlier allocations from being stripped under you when the function ends) demonstrates their "defer" mechanism. However they evidently have no way of saying, "only clean up the memory if the rest of the function is not successful".

Personally in this case, I would go for 2-goto.c. It is the cleaner design. And unlike Zig (it seems), it would be possible to nicely split up the code into an Initialize() and Run() function.

But I could be missing something. Perhaps the lifetime of the "deferred" object is tracked. Not sure how this would be possible for primitives without overhead or how this works with owning/non-owning observers. C is so much simpler = safety.
'Clean' is a very subjective word. 'Simple' is already a much less subjective word.

In 2-goto.c I count 35 sentences. (I do not count the lines with one or two symbols.)
The sentences of 3-defer.zig are often a bit longer but they are only 16 sentences, which is less than half.

I think Zig has some things where it is better than C anyway:
1. In many benchmarks Zig is simply faster than C. So for some types of programs Zig will outperform C. (One of the big reasons people still use C over other languages is that C is perceived to be faster)
2. Build times of Zig are on average 3 times faster than C in certain situations (https://github.com/nordlow/compiler...3f1512567cac79d0ddc062b3a11258d89c3/README.md).
3. Zig is very explicit about when memory management is needed. And this is where Zig really shines. Making sure you don’t forget to deallocate memory is hard in C.
4. Zig copies the defer concept from Go. But in addition to defer it has errdefer.
Why is that so great? Because it allows you to make sure some code gets run regardless of whatever convoluted if-else-statement used before exiting the function.
5. It has very few keywords so it's a easier to learn than C, C++ or Rust. The language is very small and very consistent.
6. Less buggy than C.
7. Easier to maintain than C.
8. Zig uses conditional compilation, which eliminates the need for a preprocessor as found in C. Zig makes compile-time computing a central feature instead of an afterthought. This allows Zig developers "to write generic code and do meta programming without having any explicit support for generics or templates.”
 
In 2-goto.c I count 35 sentences. (I do not count the lines with one or two symbols.)

The sentences of 3-defer.zig are often a bit longer but they are only 16 sentences, which is less than half.
Yes but the C one would work when integrated within a large program. The Zig one would return deleted memory and crash the program. XD
Give me 35 *working* lines any day.

This relates to point 4 in that later list. errdefer vs defer is fairly nasty as a solution.

I think Zig has some things where it is better than C anyway:
You mentioned you aren't really into software development so this will be difficult to debate. That said, I disagree with most of those points.

1) Zig will never be faster than C because it it will need to i.e marshal data into the format C requires for system libraries.
2) In certain situations perhaps... But others (such as compiling all the glue / binding layers), it will take *much* longer.
3) Zig will never be more explicit than C when comes to memory management. Avoiding leaks is hard in C. It is even harder when you chuck another language into the mix and have to share raw memory between them
4) errdefer and defer are not feasible for complex structures and lifetimes
5) ??? No. I don't feel that having few keywords like Javascript is an argument.
6) Maintaining binding glue is very buggy.
7) With Zig, you need to maintain a Zig compiler *and* a C compiler *and* binding glue layers.
8) Actually, it more inherits it from the fact that C does the complex platform specific parts in its binding glue layers for it.
 
Yes but the C one would work when integrated within a large program. The Zig one would return deleted memory and crash the program. XD
Give me 35 *working* lines any day.
It seems strange to me that they would quote two solutions that are not equivalent.
More generally, I do think it's true that Zig requires less code to achieve the same results with C libraries or just your code in general.

Here's another example: https://gist.github.com/andrewrk/bb124c8de6cf78ad2858041a980951d2
test_rand.c = 133 lines
test_rand.zig = 54 lines
 
It seems strange to me that they would quote two solutions that are not equivalent.
Because they are the underdog and are trying to prove their project is relevant using very contrived examples ;)

Here's another example: https://gist.github.com/andrewrk/bb124c8de6cf78ad2858041a980951d2
test_rand.c = 133 lines
test_rand.zig = 54 lines
https://gist.github.com/andrewrk/bb124c8de6cf78ad2858041a980951d2#file-test_rand-zig-L4

// see rand.zig in zig standard library for code. equivalent to c version in this gist

They are using a library that specifically provides functionality for this "test". Add these lines to your count: https://github.com/ziglang/zig/blob/master/lib/std/rand.zig

test_rand.c = 133 lines
test_rand.zig = 529 lines

Actually, that zig library binding to rand includes lots more .zig files *and* C headers. So it is well over 10000 lines!

Also, you should ask yourself why the C implementation has additional whitespace between functions and also puts { on a separate line whereas the Zig equivalent code doesn't.


More generally, I do think it's true that Zig requires less code to achieve the same results with C libraries or just your code in general.
If a language doesn't call into the underlying system (written in C). Then yes, I agree. It is possible. However real software is more complex than that. Bindings *seriously* inflate code size. Zig can't win with this approach. C++, D, Objective-C have the upper edge here.
 
Wow! Although I was hesitant to start this thread at first, I'm so glad now because I've heard so many good and new things and experiences reading all your comments!
 
If a language doesn't call into the underlying system (written in C). Then yes, I agree. It is possible. However real software is more complex than that. Bindings *seriously* inflate code size. Zig can't win with this approach. C++, D, Objective-C have the upper edge here.
I find precisely that libraries often seem not to be used for the most innovative software. I wonder if software like the popular Ansible uses C libraries?

Python 87.9% PowerShell 7.0% Shell 2.6% C# 2.1% Jinja 0.4% Go 0.0%

C ??

But you also literally say the following: Zig will never be faster than C because it it will need to i.e marshal data into the format C requires for system libraries.
There are the following things I think about this:
1. You shouldn't really use libraries to write new software. For example, if you use REBOL you are so productive that you write your software and libraries faster than if you use C and fall back on libraries. Simply because REBOL is many times more productive than C.
2. There is also a lot of new software that does not fall back on C libraries, but is largely written in programming language X, Y, Z
3. You can still translate the code of those libraries to Zig with Zig, do some optimizations, then use Zig libraries.
4. The goal of Zig is to still be compatible with C, but meanwhile to program the new libraries all in Zig, so that the old C libraries soon lose their importance and after X number of years are no longer needed for most software.

Finally, there is one final caveat I have to that statement:

Zig is faster than C. This is partly a result of Andrew’s data-oriented design approach that led to performance-enhancing changes in Zig programming that would not be possible in other languages.

Andrew’s model of how the cache system of CPUs work is intuitive: The less memory is touched, the less pressure there will be on the CPU. Working from this observation, Andrew focused on reducing the amount of memory used by objects created in Zig’s self-hosted compiler. This means that less memory is used in the compiler and that there is less pressure on the cache of the CPU, making the code faster and improving Zig’s speed by as much as 35%.

This type of optimization, Andrew explains, would not be possible in languages like Rust. One of the core components of Zig’s boost in performance is untagged unions. By putting the tags in a separate array, pressure on the cache is reduced. In Rust, however, you can’t use untagged unions without making your code unsafe.

So, here is the trade-off in Rust: Write safe code that is faster, but lose the ability to fully exploit the hardware of your computer, or write unsafe code with full performance. Zig differs from Rust in that it allows for both: Users can write faster code that is made safe through safety checks on untagged unions.

Zig’s more incremental approach to safety has allowed it to avoid the pitfalls of a safety design that has to conform to a “grand universal scheme”.
 
Zig is faster than C.
I doubt we can say that "language A" is faster than "language B". It's rather about compiler optimizations capabilities. And ease of these optimizations depends how "close" is the language to the underlying hardware.
Given that C is ~50yo and thousands developers spent millions years making compilers better (at least for x86/x86_64 arch) - it's really challenging task to beat C compilers in generating performant code.
 
it's really challenging task to beat C compilers in generating performant code.
There are ways. Having a language where some things are not explicit (like the sync points and memory layout in C), a compiler can do things that are forbidden to C compilers. When it comes to numeric software, it is really hard to beat Fortran compilers, if at all. They can do things not available to C because of things like alias rules. Java may reshuffle elements of structures at startup, optimizing for cache layouts. For such analysis, I suggest valgrind. It's worth a lot.
 
Python 87.9% PowerShell 7.0% Shell 2.6% C# 2.1% Jinja 0.4% Go 0.0%

C ??
Where is C? Haha.

Python - 37% Written in C - So ~350k lines of C code
C# - 18% written in C and C++ - So 2m+ lines of C/C++ code
Shell - 66% written in C ~ 5k lines of code

Check out this code file for the C#.NET garbage collector alone (careful its size doesn't freeze your browser): https://github.com/dotnet/runtime/blob/main/src/coreclr/gc/gc.cpp

So ansible effectively relies on more lines of C code then most people could ever write in a lifetime. Utterly dwarfing the actual implementation written in other languages... and that isn't taking into account the dependencies that Python, C# use. Many are written in C too (with bindings glue between them, also written in C).

But you also literally say the following: Zig will never be faster than C because it it will need to i.e marshal data into the format C requires for system libraries.
There are the following things I think about this:
1. You shouldn't really use libraries to write new software. For example, if you use REBOL you are so productive that you write your software and libraries faster than if you use C and fall back on libraries. Simply because REBOL is many times more productive than C.
Indeed. You should use C libraries. But that doesn't make Zig fast does it? (Rebol, 87% written in C. Its a glorified text parser)

2. There is also a lot of new software that does not fall back on C libraries, but is largely written in programming language X, Y, Z
Barely. Do a scan in the ports collection (ignoring bindings) and you will see 90%+ of libs are C (not even C++). There is a technical reason for this; if you want Python and C# to communicate directly with one another... you can't, you need to go through C because ultimately both are just glorified config file parsers written in C/C++. Only C can guarantee your library can be consumed by other common languages.

You can't escape C. But you can simplify by cutting out the noise and just using C directly.

3. You can still translate the code of those libraries to Zig with Zig, do some optimizations, then use Zig libraries.
This doesn't work well with MACROs or lifetimes of data. The Swift guys have said for years that their translator (similar approach) is good enough but Apple recently came up with a new strategy for Swift to interop with C/C++ better. They have *finally* realized they will never escape the glory that is C. Showing that regardless of what the evangelists were saying; it wasn't good enough.

https://devclass.com/2023/06/06/foc...swiftdata-object-relational-mapping-and-more/

4. The goal of Zig is to still be compatible with C, but meanwhile to program the new libraries all in Zig, so that the old C libraries soon lose their importance and after X number of years are no longer needed for most software.
I think Rust has made better progress. But even so, this will not be in our lifespans. I suspect it won't be in our grandkids lives either. I wonder when it will be? Perhaps when quantum PCs are mainstream? Either way, Zig will be gone long before. I am also convinced that the first mainstream commercial quantum compiler will be C with quantum extensions (C/q)
 
So ansible effectively relies on more lines of C code then most people could ever write in a lifetime. Utterly dwarfing the actual implementation written in other languages... and that isn't taking into account the dependencies that Python, C# use. Many are written in C too (with bindings glue between them, also written in C).
I've used Ansible to manage Ubuntu systems through a FreeBSD system. I've always found Ansible to be very slow and unresponsive.
I always thought it would be mostly written purely in Python, because it's so slow for an app that could be very lightweight.
I also think there's enough Python in Ansible to slow things down a lot:

Rebol, 87% written in C. Its a glorified text parser
It is a bit more than a glorified text parser. You probably know as everyone else that many employee bonuses are based on employee productivity. Well, in the top 20 of the programming languages that are now popular, I don't see anything that comes close to the productivity of REBOL. If the line were to continue and the focus was on employee productivity, REBOL would be the most popular language right now. It's also well suited to the areas of scripting where Python is now popular, and while it's not a super fast language, it's usually faster than Python. REBOL is also a bit more than a text parser, you can program most apps that currently exists in REBOL. REBOL can not only create a graphical user interface in one line of code, but it can also send that line as data to be processed and displayed on other Internet computer systems around the world. REBOL's consistent architecture provides powerful range of capabilities, from its small kernel interpreter (called REBOL/Core) up to an entire Internet Operating System (called REBOL/IOS). Unlike other approaches that require tens of megabytes of code, layers upon layers of complexity that run on only a single platform, and specialized programming tools, REBOL is small, portable, and easy to manage. Most REBOL applications, even serious applications that do useful things like build web sites, process credit cards, or share a calendar, are only 10-30 KB. REBOL also introduces the concept of dialecting. Undisputably, REBOL is the simplest and most powerful language by far. So why on earth didn’t Rebol take off ? Well it wasn’t opensource at the beginning and there are no leading companies that have started using it. Those are the only two major reasons why REBOL has not become the most popular programming language.

I think Rust has made better progress. But even so, this will not be in our lifespans. I suspect it won't be in our grandkids lives either. I wonder when it will be? Perhaps when quantum PCs are mainstream? Either way, Zig will be gone long before. I am also convinced that the first mainstream commercial quantum compiler will be C with quantum extensions (C/q)
It is already quite popular for a language that appeared only 7 years ago. If you see that large companies are switching to it, it can actually go very quickly.
If a second large company such as Samsung, Huawei, Meta, Sony, Hitachi, Intel, or IBM also starts using Zig, I see a bright future for this language.

it's really challenging task to beat C compilers in generating performant code.
It is not in a single benchmark where Zig is faster than C. Zig is faster than C in many benchmarks. Some examples:
nsieve
nbody
spectral-norm
helloworld
..


Rank Language Score
1 Zig 10205
2 Rust 5857
3 C 1600
4 C++ 1564
5 Java 1457

Actually, that zig library binding to rand includes lots more .zig files *and* C headers. So it is well over 10000 lines!
Do you actually agree or disagree with the Zig team's claim that their code is more compact/simpler?
I can't make any statements about it, but I see the following thing.
spectral norm
My impression is that as an app gets bigger, C requires a lot more code. But since the code can fall back on other libraries/code I can't say for sure.
But it's also what the Zig team claims (that Zig has simpler code than C).
 
I link ansible. It's easy. You can configure many things with it. It's not heavy like puppet or chef.
Just the spaces and tabs can be picky.
 
I link ansible. It's easy. You can configure many things with it. It's not heavy like puppet or chef.
Just the spaces and tabs can be picky.
It is simple, easy to use and powerful. it's also incredibly slow.

You may notice this less because you have very recent hardware. I'm sure you would also notice it on the hardware systems I've already tested with Ansible.
Furthermore, there are also many specific things that can slow down Ansible even more than it performs by default.
..
 
I find precisely that libraries often seem not to be used for the most innovative software. I wonder if software like the popular Ansible uses C libraries?
Large-scale operations these days are mostly about Terrible (Terraform + Ansible), if not Kybernetes. A useful language for that is Golang. Python's limitations really opened a gap for Go.
 
...There is a technical reason for this; if you want Python and C# to communicate directly with one another... you can't, you need to go through C because ultimately both are just glorified config file parsers written in C/C++. Only C can guarantee your library can be consumed by other common languages.
I largely agree with you, but this is a bit harsh. There are things written in pure C# and Python. Yes, I know both languages use C under the covers.

You can't escape C. But you can simplify by cutting out the noise and just using C directly...
The Rust guys sure are trying.
 
I largely agree with you, but this is a bit harsh. There are things written in pure C# and Python. Yes, I know both languages use C under the covers.
True, but in some ways overemphasizing this issue is useful when trying to explain the situation to potential non-native developers. In particular, it goes beyond "language" and more into the realms of nitty gritty implementation details.

You also have this strange situation where you create a tiny VM / interpreter (i.e Lua, CPython) which is very portable but then relies on C libraries (and bindings) to "do" stuff outside of text processing. Or on the other side, you have a large (but difficult to port) VM such as Java's JRE where you can do more (without C library bindings) so end up with more "pure" language libraries. It is quite a difficult balancing act and ultimately the larger VM is really just a big old load of "official" bindings to C libraries underneath anyway.

The Rust guys sure are trying.
They really are. Libraries like librsvg is promising. But their crates.io cesspit does tend to undermine this because it is so easy for developers to drag in a round(ish) rock and use that rather than invent a proper wheel.
 
I am also convinced that the first mainstream commercial quantum compiler will be C with quantum extensions (C/q)

Quilc is an advanced optimizing compiler for the quantum instruction language Quil, licensed under the Apache 2.0 license. Quilc comprises two projects. The first, cl-quil, does the heavy lifting of parsing, compiling, and optimizing Quil code. The second, quilc, presents an external interface for using cl-quil, either using the binary quilcapplication directly, or alternatively by communicating with an RPCQ server. Quil is the quantum instruction language, originally developed at Rigetti Computing. In Quil quantum algorithms are expressed using Quil's standard gates and instructions. One can also use Quil's DEFGATE to define new non-standard gates, and DEFCIRCUIT to build a named circuit that can be referenced elsewhere in Quil code (analogous to a function in most other programming languages).

In this post, we’ll walk through what discrete compilation is, how Coalton made it simpler to implement (compared to Common Lisp), and how we tested that such a complicated feature actually works.
 
Back
Top