A Rusty Response: "Why I think C++ is still a desirable coding platform compared to Rust"
If you can't tell, I love Rust! It's a fantastic programming language with a vibrant, welcoming community and fresh technical content to discover!
However, some traditional users feel slightly out of their depth for the same reasons! The interconnected community can feel fast-paced, leaving old projects to rot in Editions past and creating new, improved libraries with each passing moment.
As large ecosystems slowly crumble, new ones rise from the ashes. The weirdest part of this situation comes from abandoned projects. They're still perfectly usable in all of your projects, even when it's on a different Edition. You will, however, horrify some contributors...
From the static, solemn realm of C - or the slow, stable consensus of C++ and its committees - Rust appears to move at lightspeed!
Okay, okay..! Let's start with the actual content!
Henrique Bucher wrote a blog post titled Why I think C++ is still a desirable coding platform compared to Rust. Let's take a look at this article and see what we can learn! I'll respond to the paper as-is when writing, so visit this archived version for a 'perfect' match in context.
Also, please remember that I will only show part of the page. You'll need to read it for full context - and it's a decent article!
The Front Cover
The title of this article is perfect. It isn't inflammatory or inaccurate language - it just wants to say that C++ has some good points.
The "Unsafe" Excuse
Unsafe Rust has a minimal set of helpful superpowers. It's far from an optimization strategy on its own, but it helps make the language work for writing code that the compiler can't check!
For example, you can't write to some pin on a microcontroller and have the compiler know it will work. That's a 'you' problem!
Calling “unsafe" as an escape for Rust's rigid system is the same as saying that C++ can do inline assembly so you can copy/paste infinite optimizations done outside the compiler infrastructure. That defeats the purpose. We are analyzing what the language can do in normal operation is, not the backdoors that it allows to open when it gives in to operational pressure.
I don't know if this analogy works correctly. Inline assembly is rather unportable, but unsafe Rust always works when you check manually and seal it behind an internal implementation. It's your job to avoid invariance, but portability is generally acceptable after you're all done!
However, Rust indeed starts to lose value when you need to skirt unsafe rules often. In that case, languages like C or C++ are easier to deal with for these operations in particular, though I'd choose to push through! 😄
Below, I took a picture from an article on ResearchGate about loop optimization. Notice that in the first “Clang" box no optimization is being done, only preprocessing (macros, etc.) and desugaring (e.g., lambdas). All optimizations are done in the second box, where C++ no longer exists as a language.
The same logic applies to languages like Julia, Scala, and Rust. Rust adds three extra "IR" layers (HLIR, THIR, MIR) in successive lowering steps. Still, these extra layers are related to type checking and safety mechanisms (e.g., the well-known Rust's borrow checker).
A parenthesis here. Rust claims that there are "Rust-specific optimizations" done at MIR level that will, in the future, impact performance significantly. I have found a list of such transformations, but I could not assess nor prove that the performance impact is real or just rhetorical. My post on Reddit asking for comments on the subject has also not produced any conclusive proof either way, so even the experts are on the fence on this one. I feel it was more wishful thinking on the part of the documentation writer at the time.
I can only talk about some of this - I still need to familiarize myself with how C++ does its compilation. I do know about Rust's MIR, though!
As the Project's introductory blog post noted, Rust's MIR layer generally intends to aid in compilation times and precision type-checking. That post mentioned theoretical runtime performance improvements waiting for tangible changes, including non-zeroing drops and
You can find some good threads to follow using the Rust issue tracker!
Reasons for C++
Performance is not only instruction by instruction execution time-stamping. Before one gets to the actual instructions, an entire pipeline of human and machine interactions comes into play. Some of these factors will be discussed below...
Sure, I understand the need for high-performance applications in economic fields! However, when writing
std C++, you tend to avoid getting that. The fantastic Amos (from fasterthanlime) made a nuanced video on this subject, though with a broader scope than this article.
Amazon's incredible AWS also spoke on Rust's time and energy efficiency before using it across all their systems!
Many developers enjoy knowing that the compiler checks their assumptions. The unwieldy, awkward
unsafe functions don't ruin the language - especially when writing mission-critical systems!
Still, some folks like having that low-level feel from C and, sometimes, C++.
The Safety Toll
Rust tends to be more strict than C++ - it is its raison d'etre - and that means more real-time checks. Although integer underflows and overflows are only checked in debug mode, memory accesses in arrays are constantly checked for bounds unless you're in unsafe mode, which defeats the purpose. Those checks alone take a significant toll. They slow down the process compared to the respective, naturally unchecked, C++ code.
But is that an apples-to-apples comparison? Well, yes, if you are going to list the "safety" of Rust compared to C++ as a pro, then it's just fair to list the performance hit of such safety in regards to execution speed.
Yes, Rust is more strict than C++ when compiled with default options.
Pro-tip: when writing good C++ code, grab some compiler flags! If I must write C++, I tend to use these flags with Clang:
-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=2 -fstack-protector -Wall -Wextra -pedantic -fsanitize=undefined -Og -lstdc++ -ffsanitize=address,undefined
Anyways, back to the topic at hand...
With its runtime checks, Rust prevents buffer overflow attacks entirely! However, if you need to get your hands dirty, you can remove them without using unsafe blocks. The Rusty optimizations shown in that link are hugging the hardware - it feels like C, but without the headache. Best of all, there's no need to use lengthy, annoying workarounds - you're just implementing basic logic.
If you need absolute performance, you can also disable panics and ask your program to abort instead.
...[please see the original article for an example]...
However, when you consider the edge cases, things are not so clear. What happens if you multiply 2^30 by two and divide by two? 2^30 times two is 0x80000000 or -2147483648, a negative integer. Divided by two, it is -1073741824 and not 1073741824. That’s a big difference!
So why C++ ignored that possible edge case? Because signed overflow in C++ is undefined behavior and as far as the compiler is concerned, it will never happen. With the edge case reasoned away by the language, the compiler is free to implement that optimization.
If you compile a similar function in Rust, you will see that Rust will be unable to optimize that expression away because both signed and unsigned overflows in Rust are well defined in the language as a two’s complement. This results in the assembly below where in the first line (leal) the result of the multiplication by two is computed (in fact it computes value+value instead of value times two) and then the result is halved by shifting it right (sar).
I appreciate your points on undefined behavior! However, Rust now performs this operation as expected!
Sorry, but I don't know enough about this to speak about it. It's an interesting point, though!
So if I want to bet safe in the development of a new system, I have to go with C++.
I understand your ideas here! Rust is still a growing language - it has yet to take over the world.
However, it's worth remembering that writing C and C++ differs from writing exceptional C and C++. These languages are deeply complex - top engineers at Google found a significant decrease in memory safety vulnerabilities after adopting Rust for mission-critical components! Microsoft found similar results with their internal teams.
Still, your point about talent is a good one. That's why helping new folks learn the language is so important!
The Dubious Benefits of Safety
In many years of coding C++, very rarely I experienced a stack overflow or segmentation fault. It is literally not an issue in every codebase I have worked with.
Are we talking about safety against hackers? The large majority of C++ applications are non-public facing. So much that most datacenter machines run with mitigations turned off since there is absolutely no possibility of contact of those machines with bad actors. So hacker safety is not a concern that I, in particular, would care unless I’m coding a web server.
I understand your point about having systems that are 'good enough.' However, Rust's memory safety is as much about comfort as it is protection.
When your code passes the Rust compiler, you know that any remaining mistakes are logical errors. With Rust's testing support, you can trivially test your logic, too! (note: Jon Gjengset's book Rust for Rustaceans has a glorious chapter on writing good tests!)
I often point to this article about Princeton’s unmanned vehicle team competing in the 2007’s DARPA challenge as an example of how even a heavily protected, garbage collected language as C# can crash and burn, leaving your process unusable.
The article you linked is pretty exciting! However, it talks about memory leaks. These are generally caught by Clippy or
rustc itself! Miri can step in and finish everything for good when they don't.
However segfaults can be caught with a signal trap and handled cleanly like any Java/C# exceptions.
Exceptions are never handled cleanly - in any language. Rust has
catch_unwind, but developers tend to use it so worker threads don't blow up the main thread and cause weird handling.
In other languages, exceptions tend to act like good old
GOTO statements. Instead of being exceptional, they happen everywhere - another fasterthanlime article details API design and how errors can make interfaces feel... dreadful.
Still, your point is taken! C can use signal traps to perform some operations 'safely,' though the solution is platform-dependent and a bit janky. I'd be using something else for economic/systems programming purposes.
While these points are worth considering, I'm unsure how general they are.
I still can't imagine using a language like C++ for programming systems used for economics. It's still desirable sometimes, especially when you already have so much put into it.
There's a special place in my heart for C and C++, but in mission-critical applications, their half-century of technical debt becomes a barrier to innovation and maintenance.
Rust is a complex language with many unique features to familiarize yourself with. However, its advantages are profoundly beneficial in most applications. Developers gain a deeper trust in their applications, have more insight into their code, and get modern comfort features that sometimes outshine languages like Python and Go!
Rust is ready for production use. While everyone should consider a project's bounds and requirements, Rust is often an excellent choice - no matter the situation!
Thank you for taking a look at my article!
Check out Henrique Bucher's article and blog if you haven't already - they're great reads!