I am not going to lie: I love programming in C.
I know it’s a little bit irrational, but programming in C just feels right. Perhaps that’s because I did spend a lot of time programming with C as I contributed to the Linux kernel. Those were good times because I had the opportunity to learn from some of the best C programmers in the world. Hacking on the Linux kernel also taught me the connection between C programs and the machine code it compiles to, which I guess also influenced my thinking.
However, C is an unsafe language. It’s easy to make mistakes. But I spent so many hours on debugging C programs that at some point I ended up being reasonably good at it. I was often able to trace back what happened just from Linux kernel register dump (or “oops” as they call it) and fix a bug that I was not able to reproduce. I also learned to write C in a way that minimized the mistakes. I felt productive with C.
C is also a low level language. When you’re programming in C, you end up writing a lot of code just to do the simplest of tasks. There are no built-in data types so I wanted a hash table or a linked list, I usually ended up rolling my own. And I almost never used a binary search tree because writing one yourself took forever to get right. So perhaps C wasn’t perfect after all?
I did end up writing quite a bit of C++ professionally on two different occasions. The modernization effort that went into C++11 and subsequent versions made a big difference. You now had access to a variety of data structures and algorithms and with things like classes and templates, you ended up writing much less code. But C++ inherited the unsafety of C, which meant you still got to spend a bunch of time debugging your code.
# Rust’s memory safety and complexity
When Rust appeared in 2015, I was really drawn to its promise of memory safety. I had done some Haskell programming around that time and felt that both languages — although designing for completely different use cases — shared the “if it compiles, it works” mindset. The fact that the borrow checker would be able to eliminate a class of issues that you’d encounter in C all the time felt magical. But after exploring the language for some time, I gave up because the tooling was changing all the time, and — quite frankly — I could not wrap my head around the borrow checker. Perhaps I was thinking in C too much.
Fast-forward to 2021, I started working with Rust professionally. I knew I didn’t want to use C or C++ and Glauber — my co-founder — had good experiences with Rust, so we went with that. I was probably fighting with the borrow checker for two to three months until things started to click. I was thinking in C too much. I then discovered the Send
and Sync
traits and how they really help you with multi-threaded programs. The tooling and the ecosystem had also really grown up since 2015. After the initial learning curve, programming in Rust became really productive!
But Rust is a complex language. The more I write Rust, the more I feel that there is too much complexity. When you use traits or async Rust, things start to feel complicated. Perhaps I am still thinking in C too much.
# Zig as the modern C?
I think sometime in 2022 I discovered Zig because of two projects: Bun and TigerBeetle. As I had done with Rust in 2015, I started to explore it. I quickly discovered that I really like Zig because it feels like C! But just as with Rust, after initial exploration, I gave up on it because the tooling kept changing all the time and the ecosystem just wasn’t there for me.
In the summer of 2023, I wanted to give Zig another chance, partly because Glauber would not shut about it. I decided I would write a small proof-of-concept, a clone of SQLite in Zig. As I started the work, I discovered that Zig is a low level language. Not low level in the way C is, but much lower level than Rust. But I figured that’s an acceptable price to pay, because Zig just feels right. As I was working on a database, I needed a way to perform I/O efficiently. I was reading the TigerBeetle source code and then discovered Michell’s libxev and even contributed to it a bit. But after three months of weekend hacks, I ended up converting the code to Rust, and felt productive again.
# Why I am not ready to switch to Zig just yet
So why did I end up ditching Zig in favor of Rust, although I still like Zig a lot?
For starters, comptime feels like a hack. This is probably irrational, but I just don’t like comptime very much. In fact, I much would prefer the legacy preprocessor macros C if I can’t have generics. But I think I should have generics in Zig.
You still end up chasing SIGSEGVs. I don’t know if that was me thinking too much in Rust, but I ended up writing lots of silly bugs that ended up in segmentation faults that I then chased with the debugger. I assume this would get better over time if I wrote more Zig, of course, but I’ve grown to trust the borrow checker.
You also end up leaking memory. Again, this is probably me thinking too much in Rust, but I also ended up writing code that leaked memory all the time. Of course, Zig has really nice tooling here to catch them quickly, so perhaps not such a big issue.
There are no books on Zig. I really, really need a good technical book to learn a programming language, but there are none for Zig. In contrast, there’s lots of great books on Rust and specific topics on Rust available. It just feels very hard to learn Zig.
Ecosystem is perhaps not there. There are two large Zig projects, Bun and TigerBeetle, and they’re great source trees to learn from. But there does not yet seem to be a large ecosystem to tap into like there is with Rust. Tooling is also perhaps not there yet. The toolchain Zig itself provides is great, but it still keeps changing a lot like Rust back in 2015. But more importantly, third-party tools like Github Copilot
Industry investment is not there. Rust is used across our industry. For me, the tipping points were when Microsoft started writing parts of Windows in Rust and Linux kernel added support for Rust. These two things alone mean that Rust is likely going to stay around for a very long time. In contrast, Zig does not yet have the level of industry backing right now. It does not mean that Zig won’t eventually get there, but Rust is a much safer (and boring?) option right now.
# Summary
I really, really like Zig. But I am not ready to switch to it just yet. Rust is not perfect, but it’s now my go-to language for systems programming. I feel productive with it. Zig may be in the situation Rust was in 2015 and all it needs is more time. But for me, I will be waiting for the first book to come out, and then get back to it. I am also hoping that maybe they will dumb down the language for people like me, and provide an alternative to comptime. But until then, I will remain a happy Rustacean.