Zig cc: A drop-in replacement for GCC/Clang
andrewkelley.me
andrewkelley.me
> Compare this to downloading Clang, which has 380 MiB Linux-distribution-specific tarballs. Zig's Linux tarballs are fully statically linked, and therefore work correctly on all Linux distributions. The size difference here comes because the Clang tarball ships with more utilities than a C compiler, as well as pre-compiled static libraries for both LLVM and Clang. Zig does not ship with any pre-compiled libraries; instead it ships with source code, and builds what it needs on-the-fly.
Hot damn! You had me at Hello, World!
e: I didn't realise it was 5am, so I'm sorry if it's not very coherent.
alx@foo:~$ readelf -l $(which gcc)
Elf file type is EXEC (Executable file)
Entry point 0x467de0
There are 10 program headers, starting at offset 64
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
PHDR 0x0000000000000040 0x0000000000400040 0x0000000000400040
0x0000000000000230 0x0000000000000230 R 0x8
INTERP 0x0000000000000270 0x0000000000400270 0x0000000000400270
0x000000000000001c 0x000000000000001c R 0x1
[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000
0x00000000000fa8f4 0x00000000000fa8f4 R E 0x200000
you can see that in the ELF header is a field called "INTERP" that requests the loader. This is because the program has been compiled with the -fPIE flag, which requests a "Position Independent Executable". This means that each section in the code has been compiled so that they don't expect a set position in memory for the other sections. In other words, you can't just run it on a UNIX computer and expect it to work, it relies on another library, to load each section, and tell the other sections where to load it.The problem with this is that the musl loader (I don't have my x200 available right now to copy some output from it to illustrate the difference) is usually at a different place in memory. What this means is that when the program is run, the ELF loader tries to find the program interpreter to execute the program, because musl libc's program interpreter is at a different place and name in the filesystem hierarchy, it fails to execute the program, and returns "Not a valid executable".
Now you would think a naive solution would be to symlink the musl libc loader to the expected position in the filesystem hierarchy. The problem with this is illustrated when you look at the other dependencies and symbols exported in the program. Let's have a look:
alx@foo:~$ readelf -s $(which gcc)
Symbol table '.dynsym' contains 153 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __strcat_chk@GLIBC_2.3.4 (2)
2: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __uflow@GLIBC_2.2.5 (3)
3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND mkstemps@GLIBC_2.11 (4)
4: 0000000000000000 0 FUNC GLOBAL DEFAULT UND getenv@GLIBC_2.2.5 (3)
5: 0000000000000000 0 FUNC GLOBAL DEFAULT UND dl_iterate_phdr@GLIBC_2.2.5 (3)
6: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __snprintf_chk@GLIBC_2.3.4 (2)
7: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __pthread_key_create
8: 0000000000000000 0 FUNC GLOBAL DEFAULT UND putchar@GLIBC_2.2.5 (3)
9: 0000000000000000 0 FUNC GLOBAL DEFAULT UND strcasecmp@GLIBC_2.2.5 (3)
As you can see, the program not only expects a GNU program interpreter, but the symbols the program has been linked against expect GLIBC_2.2.5 version numbers as part of the exported symbols (Although I cannot recall if this causes a problem or not, memory says it does, but you'd be better off reading the ELF specification at this point, which you can find here: https://refspecs.linuxfoundation.org/LSB_2.1.0/LSB-Core-gene...). So the ultimate result of trying to run this program on a GNU LibC system is that it fails to run, because the symbols are 'missing'. On top of this, you can see with `readelf -d` that it relies on the libc library: alx@foo:~$ readelf -d $(which gcc)
Dynamic section at offset 0xfddd8 contains 25 entries:
Tag Type Name/Value
0x0000000000000001 (NEEDED) Shared library: [libc.so.6]
0x0000000000000001 (NEEDED) Shared library: [ld-linux-x86-64.so.2]
0x000000000000000c (INIT) 0x4026a8
Unfortunately for us, the libc.so.6 binary produced by the GNU system is also symbolically incompatible with the one produced by musl, also GNU LibC defines some functions and symbols that are not in the C standard. The ultimate result of this is that you need to link statically against libc, and against the program loader, for this binary to have a chance at running on a musl system.Many, many thanks for the answer! I've already done some experimenting myself and wanted to do more, so it really means a lot to me.
http://www.muppetlabs.com/~breadbox/software/tiny/somewhat.h...
I altered a version of that ELF64 header for 64 bit, and then modified it to work under grsec's kernel patches: https://gitlab.com/snippets/1749660
Being able to bootstrap FreeBSD/amd64, Linux/arm64, and actually commonly-used OS/ARCH combinations in a few minutes is just like a dream, but it is reality for modern language users.
Even though it probably doesn't qualify this is pretty close a Canadian Cross, which for some reason is one of my favorite pieces of CS trivia. It's when you cross compile a cross compiler.
https://en.wikipedia.org/wiki/Cross_compiler#Canadian_Cross
> The term Canadian Cross came about because at the time that these issues were under discussion, Canada had three national political parties.
What are the three targets in this case? It simply isn’t relevant at all.
> this is pretty close a Canadian Cross
And on that point, your correspondent is right. The two bear no real resemblance to each other. The cross compilation approach described in the article is not something to be held in high regard. It's the result of poor design. It's a lot of work involving esoteric implementation details to solve a problem that the person using the compiler should never have encountered in the first place. It's exactly the problem that the Zig project leader is highlighting in the sticker when he contrasts Zig with Clang, etc.
The way compilers like Go and Zig work is the only reasonable way to approach cross compilation: every compiler should already be able to cross compiler.
If zig were to truly cross compile for every combination of CPU variant and supported version of every operating system, it would require terabytes of storage and already be out of date.
The sheer number of things autoconf does even for trivial programs has always been baffling to me.
It has manual memory management as well as garbage collection. You could call it hybrid memory management. You can manually delete GC objects, as well as allocate GC objects into manually allocated memory.
The Zig website says "The reference implementation uses LLVM as a backend for state of the art optimizations." However, LLVM is consistently 5% worse than the GCC toolchain at performance across multiple benchmarks. In contrast, GCC 9 and 10 officially support Dlang.
Help us update the GCC D compiler frontend to the latest DMD.
Help us merge the direct-interface-to-C++ into LLVM D Compiler main. https://github.com/Syniurge/Calypso
Help us port the standard library to WASM.
That is true, but it is ALSO true that LLVM is consistently 5% better than the GCC toolchain at performance across multiple benchmarks
I am really happy that someone is making the effort to steadily simplify systems programming rather than make it more complicated. Linux goes to such incredible lengths to be bug-for-bug backwards compatible, but then the complexities of all of our layers of libcs, shared libraries, libsystemd, dbus, etc cause unnecessary pain and breakage at every level. Furthermore, cross-compiling C code across different architectures on Linux is far harder than it needs to be. I have a feeling that there wouldn't be as much interest in the steady stream of sandboxes and virtual machines (JVM, NaCl, PNaCl, flatpak, docker, WebAssembly) if we could just simplify the layers and layers of cruft and abstractions in compiler toolchains, libc implementations, and shared libraries. Practically every laptop and server processor use the exact same amd64 architecture, but we have squandered this opportunity by adding leaky abstractions at so many levels. I can't wait until installing a program on linux is as simple as downloading a static executable and just running it and I hope zig brings this future.
[1] https://www.youtube.com/watch?v=2u2lEJv7Ukw [2] https://www.youtube.com/watch?v=5S2YArCx6vU
Because when there's a security update to (say) OpenSSL, it's better for the maintainers of just that library to push an update, as opposed to forcing every single dependent to rebuild & push a new release.
Today, with containers becoming increasingly the de facto means of deploying software, it's not so important anymore. The upgrade process is now: (1) build an updated image; (2) upgrade your deployment manifest; (3) upload your manifest to your control plane. The control plane manages the rest.
The other reason to use shared libs is for memory conservation, but except on the smallest devices, I'm not sure the average person cares about conserving a few MB of memory on 4GB+ machines anymore.
Containers are popular because shared libraries cause more trouble than they are worth.
This assertion makes no sense at all and entirely misses the whole point of shared/dynamic libraries. It's like a buzzword is a magic spell that makes some people forget the entire history and design requirements up to that very moment.
Assuming you use containers, you're likely to not log into them and keep them up to date and secure by running apt-get upgrade.
The most common workflow is indeed: build your software in your CI system, in the last step create a container with your software and its dependencies. Then update your deployment with a new version of the whole image.
A container image is for all intents and purposes the new "static binary".
Yes, technically you can look inside it, yes technically you can (and you do) use dynamic linking inside the container itself.
But as long as the workflow is the one depicted above, the environment no longer has the requirements that led to the design of dynamic linking.
It's possible to have alternative workflows for building containers: you could fiddle with layers and swap an updated base OS under a layer containing your compiled application. I don't how common is that, but I'm sure somebody will want/have to do it.
It all boils down to whether developers still maintain control over the full deployment pipeline as containers penetrate the enterprises (i.e. whether re retain the "shift to the left", another buzzword for you).
Containers are not just a technical solution, they are the embodiment of the desire of developers to free themselves from the tyranny of filing tickets and waiting days to deploy their apps. But that leaves the security departments in enterprises understandably worried as most of those developers are focused on shipping features and often neglecting (or ignoring) security concerns around things that live one layer below the application they write.
I think they still make sense for base-system libraries, but unfortunately there is no agreed upon definition of 'base-system' in the wild west of Linux.
I think that's something of an exaggeration.
Yes, containers are popular for server software, but even then it's a huge stretch to claim they are becoming de facto.
And obviously video game distribution has looked like this since basically forever as well.
There's a file hundreds of megabytes large containing all the dynamically-linked system libraries on iOS to make your apps work.
They do on XBox, Swift, iOS, Android sandboxes.
Beyond containers / isolated runtime environments, the parent comment is correct about games (specifically of the console variety) being historically nearly-always statically-linked never-updated monoliths (which is how I interpreted that comment). "Patching" a game after-the-fact was effectively unheard of until around the time of the PS3 / Xbox 360 / Wii (when Internet connectivity became more of a norm for game consoles), with the sole exception of perhaps releasing a new edition of it entirely (which would have little to no impact on the copies already sold).
Diffing two docker images to determine the differences between builds would be far less onerous than attempting to diff a new deployment against a long-lived production server.
Shared libraries are shared (obviously) and get updated, so they're mutable. Linux systems depend on a substantial amount of shared mutable state being kept consistent. This causes lots of headaches, just as it does in concurrent programming.
Static libraries do nothing of the sort. In fact, they make it practically impossible to pull it off.
There's far more to deploying software than mindlessly binding libraries.
But isn't the reason to have this fully controlled and fully configurable environment to have a proof of interworking? Because when environment is in any form different you can, and people already do, say that it's not supported.
No, because there's far more to deploying apps than copying libraries somewhere.
Which is exactly the same selling point as for static linking.
Furthermore, installing that program will (again, in 90% of cases at least) not affect my overall system configuration in any way. I can be confident that all of my other programs will continue to work as they have.
Why? Because any libraries which aren't included in the least-common-denominator version of Windows are included with the download, and are used only for that download. The libraries may shipped as DLLs next to the executable, which are technically dynamic, but it's the same concept—those DLL's are program-specific.
This ability is what I really miss when I try to switch to desktop Linux. I don't want to set up Docker containers for random desktop apps, and I don't want a given app to affect the state of my overall system. I want to download and run stuff.
---
I realize there's a couple of big caveats here. Since Windows programs aren't sandboxed, misbehaving programs absolutely can hose a system—but at least that's not the intended way things are supposed to work. I'm also skipping over runtimes such as Visual C++, but as I see it, those can almost be considered part of the OS at this point. And I can a ridiculous number of versions of MSVC installed simultaneously without issue.
One program? How nice. How about 10 or 20 programs running at the same time, and communicating between themselves over a network? And is your program configured? Can you roll back changes not only in which versions if the programs are currently running but also how they are configured?
> This ability is what I really miss when I try to switch to desktop Linux. I don't want to set up Docker containers for random desktop apps,
You're showing some ignorance and confusion. You're somehow confusing application packages and the natural consequence of backward compatibility with containers. In Linux, deploying an application is a solved problem, unlike windows. Moreover, docker is not used to run desktop applications at all. At most, tools like Canonical's Snappy are used, which enable you to run containerized applications in a completely transparent way, from installation to running.
You must have close to zero experience them because that's the norm on any software that depends on, say, third-party libraries that ship with a OS/distro.
Recommended reading: Debian's openssl package.
You can either employ an extremely disciplined SecOps team to carefully track updates and CVEs (you'd need this whether you're linking statically or dynamically) or you can use e.g. Debian to take advantage of their work to that end.
Isn't a containerized solution a good compromise here? You could use Debian on a fixed major release, be pretty sure what runs and still profit from their maintenance.
> Every single company that I used to work for had an internal version of Linux that they approved for production.
I can't deny your experience, but meanwhile I've been seeing plenty of production systems running Debian and RHEL, and admins asking us to please use the system libraries for the software we deployed there.
> Internal release cycles are disconnected from external release cycles.
That seems to me like the opposite of what you'd want if you want to keep up with CVEs. If you dynamically link system libraries you can however split the process into two: the process of installing system security updates doesn't affect your software development process for as long as they don't introduce breaking changes. Linking statically, your release cycles are instead inherently tied to security updates.
> We had to do emergency patching for CVEs and bump the versions in every service.
What is that if not tying your internal release cycles to external release cycles? The only way it isn't is if you skip updates.
> This process do not depend on Debian's (or other FOSS vendor's) release cycles and the dependencies are explicit, therefore the vulnerability assessment is simpler (as opposed to go to every server and check which version is installed). Don't you think?
I don't know, going to every server to query which versions of all your software they are running seems similarly cumbersome. Of course, if you aren't entirely cowboying it you'll have automated the deployment process whether you're updating Debian packages or using some other means of deploying your service. Using Debian also doesn't make you dependent on their release cycles. If you feel like Debian isn't responding to a vulnerability in a timely manner, you can package your own version and install that.
I'm talking about the operating system that's pretty much a major component of the backbone of the world's entire IT infrastructure, whether its directly or indirectly through downstream distros that extend Debian, such as Ubuntu. Collectively they are reported to serve over 20% of the world's websites,and consequently they are the providers and maintainers of OpenSSL that's used by them.
If we look at containers, docker hub lists that Debian container images have been downloaded over 100M times, and ubuntu container images have been downloaded over 1B times. These statistics don't track how many times derived images are downloaded.
This is only slightly more relevant for pure system administration scenarios where the machine is exclusively running software prebuilt by some third-party vendor (e.g. your average Linux distro package repo). Even then, unless you're doing blind automatic upgrades (which some shops do, but it carries its own set of risks), you're still hopefully at least testing new versions and employing some sort of well-defined deployment workflow.
Also, if that "security update" introduces a breaking change (which Shouldn't Happen™, but something something Murphy's Law something something), then - again - retesting and rebuilding a runtime environment for a dynamically-linked dependent v. rebuilding a statically-linked dependent is a distinction without a difference.
Admittedly you could put that on the Kubernetes folks, but the same problem doesn't exist with glibc.
Also known as plugins.
It's not a design flaw, it's a feature.
$ cat hello.c
#include <stdio.h>
int main() {
printf("hello world!\n");
}
$ gcc -o hello hello.c
$ ldd hello
linux-vdso.so.1 (0x00007ffff9da0000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fed449d0000)
/lib64/ld-linux-x86-64.so.2 (0x00007fed45000000)
$ gcc -o hello hello.c -static
$ ldd hello
not a dynamic executableglibc is GPL licensed, and the GPL explicitly forbids statically linking to it unless your code is GPL too.
Thus any non-GPL project has it's license tainted by the GPL if you statically link it.
It's not a technical limitation, it's a legal one.
It's FUD.
See here -
https://www.gnu.org/licenses/gpl-faq.html#LGPLStaticVsDynami...
Dynamically linking a GPL library is the same as statically linking a GPL library; the resulting executable must be GPL-licensed.
If you're the kind of person who wants static linking then you really don't want these features.
The real problem is that statically linked programs under Linux don't (didn't?) support VDSO, which means that syscalls like gettimeofday() are suddenly orders of magnitude slower.
In the end, we had to do a kind of pseudo-static linking - link everything static except glibc.
I am convinced that Drepper's insistence on dynamic linking has set the linux desktop useability and developer friendliness back literal decades.
It doesn't alter the fundamental point: shared libraries save both persistent storage and runtime memory.
Which is a significant fraction of everything even if you call simple like printf.
> It doesn't alter the fundamental point: shared libraries save both persistent storage and runtime memory.
I fail to see the argument for this. Dynamic linking deduplicates dependencies and allows code to be mapped into multiple processes "for free".
My dynamically-linked executable is 8296 bytes on disc. My statically-linked executable is 844,704 bytes on disc.
So if I had a "goodbye world" program as well, that's a saving of about 800KB on disc.
Now one can argue the economics of saving a bit under a megabyte in a time where an 8GB microSD card costs under USD5 in single quantities, but you can't argue that it's a relatively big saving.
At runtime, the dynamic version uses (according to top) 10540 KB virtual, 540 KB resident, and 436 KB shared. The static version uses 9092 KB virtual, 256 KB resident, and 188 KB shared.
I haven't investigated those numbers.
Now it seems even the most trivial of apps needs more than that just to start running, and on a workstation, less than a year old with 4 cores of i7 and 32GB of RAM, I still experience lots of lag and swapping (fast SSD helps, althougn not much) doing simple things like reading an email.
Probably the GGP said they experience lag while "doing simple things like reading an email." Now, maybe GGP meant to add "while I'm sequencing genes in the background", but since that was left out I can see how it would be confusing! :)
https://answers.microsoft.com/en-us/msoffice/forum/all/teams...
There's a screenshot in there showing it taking 22GB of RAM. I've personally never seen it go that high, but the 10-12GB of RAM that I have seen is absolutely ludicrous for a chat app. Even when it's initially started it takes over 600MB. Combine that with a few VMs that also need a few GB of RAM each, as well as another equally-bloated Electron app or two, and you can quickly get into the swapping zone.
I remember comfortably browsing webpages with lots of large images and animated GIFs in the early 2000s, with a fraction of the computing power I have today. Something has become seriously inefficient with browser-based apps.
According to Activity Monitor, right now:
• 4.26 GB are being used by apps
• 19.52 GB are cached files
• 8.22 GB are just sitting idle (!)
Now, I'm not running anything particularly intensive at the moment, and I make a point of avoiding Electron apps. I also rebooted just a few hours ago for an unrelated reason.
But the fact is that I've monitored this before—I very rarely manage to use all my RAM. The OS mostly just uses it to cache files, which I suppose is as good a use as any.
I do that personally too, but in a work environment that is unfortunately not always possible --- and also responsible for much of the RAM usage too.
What I failed to mention was that the rootfs is also eating into that (ramdisk). In your case I'm guessing your rootfs was on disk.
Could someone estimate how much software nowadays is bloated by duplicated modules?
https://stackoverflow.com/questions/3430400/linux-static-lin...
I've also linked this Zig post into that list (and happy to add further languages if you can provide a link that shows that they have good out-of-the-box static linking support).
Looking at how a browser, an IDE, and a few compilation processes will gladly chew through 8GB of memory... it’s not necessarily horrible, but this is a modern contrivance.
I never realised people were moaning about shared libraries.
(e.g. I have Firefox running under Capsicum: https://bugzilla.mozilla.org/show_bug.cgi?id=1607980)
Dynamic linking was added around Slackware 2.0 timeframe.
Other have mentioned the other points: runtime loading (plugins), CoW deduplication and thus less memory and storage.
For the record: This is pretty close to what AppImage is today. It's not quite 100% because userland fragmentation is so ridiculously bad that it doesn't work out of the box on a few of them, but I personally really wish all Linux software was distributed that way (or static like Zig).
Should every compiler stack have prioritized cross compilation over other features? (I vote: YES). Cross compiling programs has always been a PITA for most languages.
It would be great if Zig cc could be paired with vcpkg [1] for a nice cross-compiling development environment. Looks like vckpg requires a C++ compiler though.
But it's also fair to say that if we had always considered those things as inseparable parts of the "compiler suite" that might have made everyone better off.
This is what makes porting hard work. Cross-compiling is only the first step of a long trip.
You can't look at C which started in the 1970s and C++ which started in the 1980s and have expected them to even consider cross-compilation, when Autoconf wasn't even released until 1991.
Most other languages make it _possible_ to generate some sort of artifact usable from different operating systems but not necessarily easy. I think Java only relatively recently included a standard way to create a bundle including a minimal JVM distribution with an app to make it usable when the user doesn't have an installed JVM (and again, there were a bunch of different non standard solutions of varying quality before that). Even now I wouldn't say the Java solution is easy to use.
I could continue in this fashion with different languages, but you get the idea.
I heard go is pretty good in ease of cross compilation, and well, looks like Zig is doing great in this area too. Ah! .net core is apparently pretty good in this area these days too.
Well, there are web browsers and servers. They distribute javascript programs and run them.
For that reason, Java and Python didn't start out with fully self-contained bundles as a design goal. It just wasn't practical in the 90s. Obviously, yes, if they had managed to correctly predict and plan for three decades of technological improvement, then sure, we'd be working in a very different technological landscape. But they couldn't possibly have, and solutions built on the old languages are always fraught with disagreement. So, we use new languages, like Go and Rust, which are developed with modern needs in mind.
If someone could do the same but for a younger generation, I think it'd be very valuable.
Just finished reading the Zig cc article and I must say I'm also quite impressed. I'll be keeping an eye on the next Zig release--being able to eventually use it as a `cc` or `mvsc` replacement would be a big game changer. Having recently run the study of trying to cross compile a few C++ and GTK apps, I can really see the appeal.
I don't know of anyone who actually uses Rust in that niche.
Everyone who uses Rust is using it because of "C++ is hard, let's go shopping instead" syndrome.
I.e., at this point it's a language for beginners to ease themselves into programming without training wheels and eventually graduate to real big boy programming languages.
The real-world fact is that Rust is, as of March 2020 at least, an entry-level systems programming language. It's used as a stepping stone by former PHP/Python/Go programmers, who are very intimidated by C++, to get into performance-oriented coding.
Nobody actually writing embedded or sensitive code (airplanes, nuclear power stations, etc.) is doing it in Rust.
The language is young and you don't certify a software solution every two days or don't rewrite your nuclear power station code every day.
Very experienced programmers switched to Rust because it makes it possible to build large scale industrial programs both efficient and reliable. They won't switch to C++ just because they think they're good enough to live dangerously.
(btw I work on plant control and yes I write parts in Rust)
This never happens in the real world; not unless the 'very experienced' bit is experience only in languages like PHP or Python.
It's true that not a lot of people are using Rust for embedded software. That's a much harder nut to crack because so many of the toolchains are proprietary (and embedded support in Rust is still missing quite a few things).
You kind of do if you ever want to work on anything other than pet personal one-man projects.
Also: I've used Rust at work. In fact, I learnt Rust because I was processing a lot of data at work, and needed a fast language to do so in a reasonable amount of time.
Rust seems to be mostly something "full stack developers" and "back end developers" embrace for server-side toys and hobby projects.
Top-end embedded processors have GPUs and hypervisors these days and run AI algorithms to, say, detect lane changes and do parallel-parking maneuvers. These days AI means code written in Python and Fortran.
fn add(a: i32, b: i32) i32 {
return a + b;
}Rust quickly becomes harder though, while Zig is much easier to grasp.
But then, of course, it wouldn't have much to do with Zig's "appeal as better C than C", which prompted this whole discussion.
char * const (*(* const bar)[5])(int )
And its translation: declare bar as const pointer to array 5 of pointer to function (int) returning const pointer to char
This seems like what I'd get from the spiral technique. int** arr1_of_arr2_of_arr3_of_ptr_to_ptr_to_int[1][2][3]
In this case, spiraling between [1], ×, [2], ×, [3], int is obviously wrong, the correct reading order is [1], [2], [3], ×, ×, int. (Edit: read × = *)The Right-Left Rule is quoted less frequently on HN but it's a correct algorithm for deciphering C types: http://cseweb.ucsd.edu/~ricko/rt_lt.rule.html
The only thing that C has taken from Algol, was structured control and data.
Still no way to catch use-after-free.
If you have a few bucks per month to spare, consider chipping in. I'm hoping to have enough funds soon to hire a second full time developer.
On my RPi 4, 'uname -m -o' returns: armv7l GNU/Linux
Thanks!
Are there any concurrency constructs provided by the language yet? I'm just starting to learn how to do concurrency in lower-level langauges (with mutexes and spinlocks and stuff). I'm coming from the world of Python where my experience with concurrent state is limited to simple row-level locks and `with transaction.atomic():`.
An equivalent article to this would be awesome for Zig: https://begriffs.com/posts/2020-03-23-concurrent-programming...
Edit: I just found this announcement for async function support: https://ziglang.org/download/0.5.0/release-notes.html#Async-...
This area is still bleeding-edge experimental, but it's very promising.
I need to do a blog post on how async/await works in zig and event-based I/O. It's been a long time coming.
I find it hard to believe that someone could be capable of writing a non-trivial program but not able to change their text editor settings to use \n.
I'm floored that so many people would ignore this barrier to entry and somehow rationalize such a ridiculous design choice. If you make things straight up break for all windows users by default in your new language, it's going nowhere. No rationalization or excuses will change that reality.
How many developers who try out new languages are even using Windows? I'd imagine most are on a UNIX-like. Regardless, I don't think it's a big deal either way. You could always make a merge request with a fix if you feel so strongly about it.
Grand bootstrapping plan [1] sounds really impressive but still WIP? Is there a commit or series of commits showing recent targets that got support?
there are plenty of things i feel are serious shortcomings of C (mixing error results with returned values is my big one), but the fact that the set of things the language can do is small and the ways you can do them are limited makes it much easier to write code that is easy to read. and that will always keep me coming back.
Reminded me of the Stanford Builder gg[1], which does highly parallel gcc compilation on aws lambda. make -j2000.
So with a zig cc drop-in, you might get highly-parallel cross-compilation?
Though the two caching systems might be a bit redundant.
musl v1.2.0
mingw-w64 v7.0.0
glibc 2.31"
These are super-important... (Otherwise, someone will be very limited in what they can compile -- the simplest of programs only...).
It's great that you included these, and as source, not as precompiled binaries!
(Also, a well-selected subset is probably the right balance of functionality vs. complexity...)
Anyway, very excited about the future of Zig as a drop-in Clang/gcc replacement!
Otherwise: yeah. It's just clang's main function with a different name slapped on. Linking semantics may differ slightly which could be problematic. But in theory, yes.
Clang can apparently build a vanilla Linux kernel now, no gotchas, actively used by Google for its Linux things (Android, chromeos).
That would be a nice stress test, which would undoubtedly lead to bugs discovered. After enough bugs fixed, the answer would be "yes".
I'll try it!
It's a bit unfortunate that (last I checked) there's no "I don't care how it's implemented as long as it's a list" option at the moment (e.g. for libraries that don't necessarily want to be opinionated about which list implementation to use). Should be possible to implement it as a common interface the same way the allocators in Zig's stdlib each implement a common interface (by generating a struct with pointers to the relevant interface functions).
Are there any guides anywhere for calling libc functions from zig? I'm interested in fork/join, chmod, fcntl, and that kind of thing. Do I just import the C headers manually? Or is there some kind of built-in libc binding?
std.os.fork: https://github.com/ziglang/zig/blob/master/lib/std/os.zig#L2... std.os.fcntl: https://github.com/ziglang/zig/blob/master/lib/std/os.zig#L3...
std.os.chmod
std.os.fcntl
Better yet, use the higher level cross platform abstractions. For example instead of fork/join,
std.Thread.create
std.Thread.wait
These will work in Windows as well as POSIX.
Cross-compiling with clang is complex because it's just a C compiler, and doesn't make assumptions about what headers the target system might use, or what libc it's using, so you have to set all those things up separately.
Zig is (apparently) a new language built on Clang/LLVM, so it can re-use that to provide a C compiler. It also makes cross-compilation easier in two other ways. First, it limits the number of supported targets - only Linux and Windows, and on Linux only glibc and musl, and all supported on a fixed list of the most common architectures. Second, building Zig involves pre-compiling every supported libc for every supported OS and architecture, and bundling them with the downloadable Zig package. That moves a lot of work from the end user to the Zig maintainers.
Like most magic tricks there's no actual magic involved, it's just somebody doing more and harder work than you can believe anyone would reasonably do.
(Also, having undefined behaviour on unsigned overflow can make some bit twiddling code harder to write. Then again, maybe Zig has a bit-twiddling unsigned-like type without overflow check? Zip allow turning off checks, but then it turns off checks for everything...)
My only gripe is that the syntax and stdlib, although practical and to the point, seem to suffer from some strange choices that somewhat clash with its own, albeit early, "zen" of simplicity.
- '@' prefix for builtin functions, a little strange and macro-looking for my eyes. Why not just plain keywords? And cleanup some of it: `@cos`, `@sin`, also feel like too much when they are already in the stdlib I believe.
- |x| for/while list bind var, why not just for(x in y)? Surrounding pipes are really annoying to type in some foreign keyboards and feel totally needless in 99% of the places.
- inconsistent required parenthesis predicates in block statements in "test STR {}" vs. "if() {}". Either require parenthesis or don't, I don't really care which one.
- prefixed type signatures, `?[]u32` feels a little off / harder to read.
- comment-looking, noisy prefixed multi-line slashes `\\`.
- the need to dig deep into "std" to get your everyday lib functions out "std.io.getStdOut().outStream().print()". `@import("std")` repeated many times.
- consider implementing destructuring syntax early-on to deal with so much struct member depth ie `const { x, y } = p` or `const { math: { add, mul } } = @import("std")`.
- anonymous list syntax with `.{}` is eye catching as the dot implies "struct member" in Zig, but then the dot is everywhere, specially when you do anonymous structs `.{.x=123}`, maybe consider `[1,2,3]` and `[x=123]` given brackets are being used for array length annotation anyways ie `array[]`.
- `.` suffix for lvalue and rvalue pointer deref. Also `"str".` is a byte array unroll if I understood correctly. Here `f.* = Foo{ .float = 12.34 };` looks like it's doing something with `.` to get to the struct members but it's actually just a pointer deref. Also looks like a file or import lib wildcard (`file.*`) to my eyes.
- field access by string clunky `@field(p, "x") = 123;`, with an odd function as lvalue.
Sorry for the criticism, we're seriously checking out Zig for migrating a large C codebase and replacing future C projects. Although we can live with these quirks, they just make the language look a little random and NIH and that worries me and the team. For instance, Golang has great syntax and semantic consistency which is a boost on project steering quality and assured, life-long onboarding for newbees. Please consider widening the spec peer-review process, maybe in a separate Github repo with markdown proposal writeups. Discussing syntax seems superficial given many project and compiler feats under the hood, but it can become sorta "genetic disease" and a deal-breaker for the project on the long run!
This is a pre-release version I know, but it's just that my hopes are really up for Zig as Golang, C++ and Rust never really did it for us as a multi-target sw toochain for various reasons.
for (([100]void)(undefined)) |_, verb| {
And I've been bitten multiple times with line endings having to be \n only.~~Does `zig cc` cross compiler libgcc/compiler-rt on the fly? Does it compile libc on the fly?~~ Nevermind I did not scroll enough, it does compile on the fly. Whew!
As someone who also cares greatly about cross compilation, compilers definitely should step up their game, but `-target x86_64-windows-gnu` elides many details, unless you are confined to a fix set of platforms.
Zig looks like a nice language for kernel development as well: https://github.com/jzck/kernel-zig
Speed? Using Zig should be faster than using Clang directly in many cases. You get the caching system, and I think you can do more complex builds without having to resort to multiple Clang commands from a makefile.
Not sure what you mean with external libraries.
But for supporting more esoteric targets you might be interested in the goals of this ultra-early-stage assembler. ("Planned targets: All of them.")
If you tell me that I can write better, safer code by using Zig, but I can also compile it into a .c artifact that anybody can use, now that is a tempting proposition!
After you get a taste of a modern toolchain (with cross-compilation, dependency management, but withou not-quite-portable build files to endlessly fiddle with, without outdated compilers to work around), you will not want to have to compile a C file again.
Languages like Zig and Rust are easy to install. Mostly it's just a tarball, so it's less of an inconvenience than getting the right version of autotools.
Zig should be just as easy to integrate. Sure, it's one more thing to install, but it'll spit out .o files just like a C compiler would if you tell it to (which means you can shove it in your Makefile or what have you), and will spit out .h files for linking. You miss out on Zig's build system niceties that way, though (including the cross-compilation demonstrated in the article).
Regardless, being a "C replacement" kinda implies (if not outright explies) that it's replacing C; compiling to C kinda defeats that purpose. It'd still be useful, though, and is probably possible (might even be relatively trivial if LLVM and/or Clang provide some mechanism to generate C from LLVM IR or some other intermediate representation).
Zig is unable to provide a libc for the chosen target 'wasm32-wasi-musl'
Folks who have achieved great things with llvm (and C++) have done so 'despite' what they used, not 'because of' it.
This has been my conviction for the past 10 years, and I'm glad I never had to touch llvm with a ten foot pole.
I had no doubts it'll soon be surpassed by a common-sense no-bullshit tool-chain.
Has Zig cc achieved that? Great. No? It will or someone (or I) will develop an alternative that will.
I would suggest holding your convictions more loosely.
Out of curiosity: what do you touch with a ten foot pole? I'd be hard-pressed to call GCC or MSVC much better in that regard, and I can think of very few others that are in use anymore.
I mean, I've definitely dreamt about using SBCL or Clozure for things other than Lisp (seeing as they both include their own compilers not dependent on GCC/LLVM), but I've seen effectively zero effort in that direction.
Does yours do loop unrolling, code hoisting, optimise array accesses to pointer increments, common expression elimination etc?