BitBox Cold Wallet
Purchase BitBox Cold Wallet

C++ Encounters of the Rusty Zig Kind

There comes a time in any software developer’s life when they look at their achievements, the lines of code written and the programming languages they have relied on, before wondering whether there may be more out there. A programming language and its associated toolchains begin to feel like familiar, well-used tools after you use them for years, but that is no excuse to remain rusted in place. While some developers like to zigzag from one language and toolset to another, others are more conservative. My own journey took me from a childhood with QuickBasic and VisualBasic to C++ with a bit of Java, PHP, JavaScript, D and others along the way. Although I have now for years focused on C++, I’m currently getting the hang of Ada in particular, both of which tickle my inner developer in different ways. Although Java and D never quite reached their lofty promises, there are always new languages to investigate, with both Rust and Zig in particular getting a lot of attention these days. Might they be the salvation that was promised to us C-afflicted developers, and do they make you want to zigzag or ferrously oxidize? Solving Problems As hilarious it is to make new programming languages for the fun of it, there has to be some purpose to them if they want to be more than a gag. That’s why Whitespace and Brainf*ck are great for having some (educational) fun with, while Forth is a serious and very much commercially successful language. Meanwhile there’s still an ongoing debate about whether Python may or may not be an esoteric language, mostly on account of it granting whitespace so much relevance that would make the Whitespace developers proud. This contrasts heavily with languages like C and consequently C++ where whitespace is not relevant and you can write everything on a single line if that’s your kink. Meanwhile in Ada, COBOL and others case sensitivity doesn’t exist, because their developers failed to see the point of adding this ‘feature’. This leads us to another distinguishing feature of languages: weakly- versus strongly-typed and super-strongly typed languages. If one accepts that a type system is there to prevent errors, then logically the stronger the type system is, the better. This is one reason why I personally prefer TypeScript over JavaScript, why Java reflection and Objective-C messaging drove me up various walls, why my favorite scripting language is AngelScript, why I love the type system in Ada and also why I loathe whoever approved using the auto keyword in C++ outside of templates. With those lines marked, let’s see what problems Rust and Zig will solve for me. Getting Ziggy The Zig language is pretty new, having only been released in early 2016. This makes it four years younger than Rust, while also claiming to be a ‘better C’. Much of this is supposed to come from ‘improved memory safety’, which is a topic that I have addressed previously, both in the context of another ‘improved C’ language called TrapC, as well as from a security red herring point of view. Here again having a very strong type system is crucial, as this allows for the compiler as well as static and dynamic analysis tools to pick up any issues. There is also the wrinkle that C++ is already an improved C, and the C11 standard in particular addresses a lot of undefined behavior, which makes it a pretty tall order to do better than either. Fortunately Zig claims to be a practically drop-in solution for existing C and C++ code, so it should be pretty gentle to get started with. Unfortunately, this is the part where things rapidly fell apart for me. I had the idea to quickly put together a crude port of my ncurses-based UE1 emulator project, but the first surprise came after installing the toolchain. My default development environment on Windows is the Linux-like MSYS2 environment, with the Zig toolchain available via pacman. A feeling of dread began to set in while glancing at the Getting Started page, but I figured that I’d throw together a quick ncurses project based on some two-year old code that someone said had worked for them: const std = @import("std"); const c = @cImport({ @cInclude("curses.h"); }); pub fn main() !void { var e = c.initscr(); e = c.printw("Hello World !!!"); e = c.refresh(); e = c.getch(); e = c.endwin(); } Despite the symbol soup and chronic fear of fully writing out English words, it’s not too hard to understand what this code is supposed to do. The @cImport() block allows you to include C headers, which in this case allows us to import the standard ncurses header, requiring us to only link against the system ncurses library later on. What’s not inspiring much confidence is that it’s clear at this point already that Zig is a weakly-typed language, bringing back highly unwanted embedded JavaScript flashbacks. While prodding at writing a standard Makefile to compile this code, the reality of the Zig build system began to hit. You can only use the zig command, which requires a special build file written in Zig, so you have to compile Zig to compile Zig, instead of using Make, CMake, Ninja, meson, etc. as is typical. Worse is that Zig’s API is being changed constantly, so that the sample build.zig code that I had copied no longer worked and had to be updated to get the following: const std = @import("std"); pub fn build(b: *std.Build) void {   const target = b.standardTargetOptions(.{});   const optimize = b.standardOptimizeOption(.{});   const exe = b.addExecutable(.{       .name = "ncurses",       .root_source_file = b.path("main.zig"),       .target = target,       .optimize = optimize,   }); exe.linkSystemLibrary("c");   exe.linkSystemLibrary("ncurses");   b.installArtifact(exe); } With this change in place, I no longer got compile errors for the build file, but even after deleting the .zig-cache folder that the toolchain creates I kept getting the same linker errors: While I’m sure that all of this is solvable, I was looking for a solution to my problems, not to get new problems. Instead I got a lack of strong typing, an oddly verbose syntax, ever-shifting APIs, being strong-armed into giving up the build tools of one’s choosing and finally some weird linker errors that probably require constant nuking of caches as one has to already suffer through with CMake and Gradle. It is time to zigzag out of dodge to the next language. Rusted Expectations As mentioned earlier, Rust is a few years older than Zig, and in addition it has seen a lot more support from developers and companies. Its vibrant community is sure to remind you of these facts at any opportunity they get, along with how Rust cures all ills. Ignoring the obvious memory safety red herring, what problems can Rust solve for us? Following the same pattern as with Zig, we first have to set up a development environment with the Rust toolchain and the ability to use ncurses. Unlike with Zig, we apparently cannot use C (or C++) code directly, so the recommendation is to use a wrapper. From its code we can worryingly tell that it is also a weakly-typed language by the use of type inference, and the fact that the unsafe keyword is required to cooperate with C interfaces gives even great cause for concern. Ideally you’d not do the equivalent of hammering in raw assembly when writing C either, as this bypasses so many checks. Regardless, the task is to figure out how to use this ncurses-rs wrapper, despite it already being EOL-ed. Rather than dealing with this ‘cargo’ remote repository utility and reliving traumatic memories of remote artefact repositories with NodeJS, Java, etc., we’ll just copy the .rs files of the wrapper directly into the source folder of the project. It’s generally preferred to have dependencies in the source tree for security reasons unless you have some level of guarantee that the remote source will be available and always trustworthy. Although you can use the rustc compiler directly, it provides an extremely limited interface compared to e.g. Clang and GCC. After trying to understand and massage dependency paths for the included files (modules) for a while, the sad result is always another fresh series of errors, like: The frustrating end to trying out Rust.At this point any enthusiasm for doing more with Rust has already rapidly oxidized and decayed into sad shards of ferrous oxide. Workflow Expectations Most of my exposure to Rust and Zig prior to this experience had been from a theoretical and highly academical perspective, but actually trying to use a language is when you really begin to develop feelings that tell you whether the language is something you’re interested in. In my case these feelings were for both languages primarily frustration, mixed with an urge to get away from the whole thing as soon as possible. This contrasts heavily with my recent experiences with COBOL, which saw me working for days on code and figuring out the language, but with a feeling of almost giddy joy at grasping yet another concept or mechanism. What helped a lot here is that the COBOL toolchains are just typical GCC compilers with the whole feature set, which means that you can use them with any build system of your choice. Even with the Ada toolchain and its multi-step process of module dependency resolving, compiling and linking you can use these tools any way you like. It’s this kind of freedom that is at least in my view an essential part of a good development environment, as it gives the developer the choice of how to integrate these into their workflow. The workflow with Zig and Rust reminds me mostly of the harrowing struggle with Android development and its Gradle-based environment. You get similar struggles with just getting the basic thing off the ground, are always dealing with baffling errors that may or may not be related to a component that’s a few versions too old or new, and basically it’s just a gigantic waste of time. Even ignoring whether Zig and Rust are or can become good languages, it is this complete disregard for individual workflow preferences that’s probably the most off-putting to me, and reason to avoid these ecosystems at all cost. Something which I wish I could do with Gradle as well, but I digress. In the end I think I’ll be sticking with C++, with a bit of C and an increasing amount of Ada and Fortran on the side. Unless you’re being paid big bucks, there is no reason to put yourself through the suffering of a workflow you loathe.



Never forget.

Work → Buy Bitcoin → Sleep → Try Again = RICH GUY

Work → Spend → Sleep → Try Again = POOR GUY