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!

the classic Simpsons NERD GIF

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!

Let's begin!

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


Upon reading this, some pundits will throw the recurring excuse “oh but Rust has unsafe mode where you can do yada yada", ...

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! 🥹


...so let me preface this article with this analogy.

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! 😄

The Similarities


In principle, both Rust and C++ are compiled languages that use 95% of the LLVM compiler infrastructure. Rust and C++ are translated into IR, where most (arguably, all) optimizations are made.

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 Try (?) optimizations.

You can find some good threads to follow using the Rust issue tracker!

Reasons for C++


I need to recall that this publication is about low latency trading and as such our tradeoffs when deciding if something is worth it will always lean towards the item that leads to performance increases. If we were a company that produced web servers or web browsers, then the conclusions below would probably be of a different nature.

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.

Undefined Behavior


C++ relies extensively on undefined behavior as an optimization enabler. UB can make a brutal difference in many cases. Rust, on the other hand, does not and cannot leave knots untied, because its focus on security.

...[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!


Again, pundits will state that you could have called one of the arithmetic wrapping functions, which will force the compiler to do this optimization. Well you can do many things but here we are measuring the effect of the compiler over two similar blocks of code.

The wrapping functions you're talking about can be annoying. However, if you have your own types, you can use operator overloading like C++! It's surprisingly flexible.

Compiler Choice

Sorry, but I don't know enough about this to speak about it. It's an interesting point, though!


[...]

We analized this in a previous article.

gross! 😄

Resources Available


Although the scenario is changing rapidly, the pool of engineers with C++ background is much larger than the pool of Rust developers. Also, the quantity of teaching material and resources available to learn the language is currently overwhelmingly more abundant on the C++ side.

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


When Rust is brought up in a meeting, the first pro factor is language safety. But safety against what?

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!)


Or are we talking about protection against crashes? Well don’t get me started on this one. First, crashes can happen in any language, with the same frequency.

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.


Exceptions can throw a Java process back to main and helpless on how to proceed, the process will puke an undecipherable excuse and bail out. Oh but C++’s segmentation fault is much worse! say the haters.

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.

Conclusion

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!

Conclusion

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!

Please let me know if you have any corrections, comments, or ideas for this post or others. You can create a GitHub issue, email me, or even respond to this article! Thanks again! 😄