How I Learned to Stop Worrying and Love Rust
Posted by Jack on 2023-01-28 at 23:52Tagged: software , programming
C++ (still) sucks
Back in 2020, a great misfortune befell me at work, right before the pandemic.
My project, a solo effort nestled between firmware (UEFI) and OS had been beaten into shape and was working admirably. It had a flexible configuration and was filling its niche. It had matured, and the slackened maintenance burden meant I had free time.
I spent that free time exploring the adjacent problem space, targeting hard to hit CPU bugs and... suddenly, a manager realized that I had spare cycles and I got pulled into a project only tangentially related to my interests and in a language I despise, C++.
Now, I was "raised" as a developer as a kernel hacker. I'd hacked firmware / OS level C for myself and for my corporate overlords for almost 15 years and I bring the perspective of open source with me wherever I go. Before that, I spent college diligently doing my assignments in C (or LISP on occasion) except for a brief bout of Visual Studio C++ that was required for a data structures class that covered GUI programming that I absolutely hated. Linus Torvalds asserted C++ is garbage and totally unsuitable for low level developement and I took that as gospel.
So in 2020, I found myself the odd man out on a team of true believers. These engineers had spent the last 20 years writing C++ and they assured me this wasn't old, broken C++03 I learned in college, this was Modern C++. C++11, no 14, no 17, no 20. Things were different now, better. C++ had reformed. The 40 year old language got its groove back.
In the intervening three years of working daily with modern C++, multiple training seminars, and rubbing elbows with C++ gurus on my team, I'm still convinced it's terrible but my reasoning has changed. Where my experience with C++03 was that C++ was basically C with namespaces, exceptions, a half baked object system, and an awkward template system, Modern C++ is a language in a full-fledged identity crisis.
C++ has spent the last decade attempting to morph into something different. Virtually any project written old C++ is now ugly and broken. The idioms of 20 years ago have been fully abandoned, and premiere feaures have been bolted on from elsewhere. Shifting to automatic memory management, RAII, sprinkling closures into the standard library. It's trying hard to get you to forget it's still carrying all of its C baggage. Ownership is enforced, copying minimized in favor of moving, standard data types replaced with bounds-checked alternatives. The standard library has expanded to include native threading, tuples, optional return types. All, in and of themselves, not bad features to have in a language.
But nothing has been done to curb all of the old broken behavior in the name of backward compatibility and, as such, projects have to cope by using a hundred and one linters, formatters, and sanitizers to enforce all of the unwritten, idiomatic rules of the new language. All of those C data types being replaced are still there under the covers, waiting to have clang-tidy smack your hand for using them. The C Preprocessor is still there, but using variadic macros is soooo 2005 so you better only use it for simple substitutions, but only if you can't constexpr, oh and those symbols better fit the awkward naming convention or else! Passé parts of the language, like overloading basic operations are verboten, as are basic C operations like for loops to search a list (because why do something that basic when std::find and friends will let you do the same thing, but more clumsily).
I understand that I sound like a C programmer bitter at having to adapt, and that's a valid read of this post, but I'll tell you what makes working with C++ even worse than being a bitter C programmer... Learning Rust.
Rust is great
I have toyed with many programming languages, but most were out of curiosity about a different paradigm than C (LISP, Haskell, Factor) or for scriptier purposes like text processing (Python) or embeddability (Lua).
When it came to getting work done and writing high performance code though, I had mastered C and I was going to stick to it. Especially in the difficult environments I work in (where there is no OS and debugging a program often comes down to reading register and stack dumps via a hardware debugger) I appreciated that it's extremely easy to get a grip on what's going on in a C program staring at its in-memory assembly image.
Dealing with modern C++ though, I kept getting torn between "that's a really cool feature" and "my code has been linted and reviewed into something ugly and unreadable." I found myself wanting something in that mid-range between C being portable assembly and an interpreted language of convenience like Python. Something compiled and performant, but with some basic niceties like implicit memory management, error handling, and baked in basic types like vectors.
So I began writing Rust a couple of years ago and it's like a breath of fresh air. I now understand C++ is attempting to evolve into Rust, it just can't. This became especially obvious to me lately when, leaving the toy problem Project Euler universe, I started trying to do "real work" and interact with other Rust projects, as well as libraries through the Rust FFI.
Rust has no C baggage. It has no headers but an extremely powerful macro system that makes the C pre-processor look antiquated. It has implicit memory management from the ground up. Explicit mutability that C++ can only poke at with const. Traits and a trait based constraint syntax that allow much finer grain, piecemeal implementation of "classes". In many places it seems like Rust just has parity with C++ (e.g. the expanded standard library), but without the C baggage these are all wins because the language isn't littered with trapdoors that let you violate the new normal. You can't just decide to start using dumb pointers, or use C style arbitrary casts, or any of the other million things clang-tidy will complain about that are still valid C++. In fact, the only way to "escape" Rust's guarantees is to explicitly mark your code 'unsafe', something I imagine C++ committee members wish they could tuck all of their legacy, unsafe, unidiomatic, undefined behavior behind.
Outside of syntax, Rust also has an integrated build system and package manager that reminds me of the Python "batteries included" promise. Crates make it so easy to create a new Rust package or library, advertise dependencies, and even release your code. In C++ you're basically forced to use a third party tool, like CMake to build and Conan to manage packages and it complicates everything immensely, but it's just another area where C++ can't escape its age. Rust was developed in a world where all developers are online all the time and it's no longer acceptable to expect someone else (your distro, your toolchain) to have dealt with packaging and distributing your dependencies.
In my opinion there is no place where Rust isn't an improvement over C++. Perhaps compiler support (I'd like GCC Rust, which will be experimental in GCC 13.1) and optimization that comes with being a younger language, but in terms of what's on the page - the act of prototyping and writing Rust code - and dealing with the library ecosystem, Rust is a fucking slam dunk.