The Problem with C++ Safety: Why “It Works” Isn’t Good Enough
10 points by raymii
10 points by raymii
I’ve been coding in C++ since 1991, but I’ve finally given it up (when I have a choice) for either Swift or Rust. When I look at C++ code now (even my own) it looks like a wobbly assemblage of razor blades stuck together with scotch tape.
I’m surprised that the list of issues does not include the implicit narrowing conversion from the string size to an int. (Especially since the list does complain about the implicit widening conversion in the str[] indexing expressions.)
int size = str.size();
Integer conversions need a great deal of care in C and C++ so this was the first thing that caught my attention. They had a lucky escape from UB in this case since narrowing conversions are implementation-defined, and an overflow to a negative index is caught by the loop guard (because it’s designed to cope with deliberate underflow due to an empty string). But the algorithm is quietly wrong for strings longer than INT_MAX.
I’d rephrase the title to, The problem with C++ safety: “it works” is often undermined by the language.
Although the code is just a few lines long, it is already difficult to think it through in every detail. Are all special cases covered? Could this result in an infinite loop? Could this accidentally access any invalid memory location? Are there other sources of undefined behavior? I can’t be certain by just looking at it. I’d need more time, potentially much more time to really think it through.
This definition of bad code really stuck with me. I wouldn’t have really called that code bad per se, mostly because it looks almost exactly like any imperative palindrome-finding algorithm out there, but this articulates very nicely why it isn’t good.
There’s some good advice in this article which would work beyond C++. I do think projects should adopt Rust if possible.
We say that C++ is the problem, but that's only half of the picture. With Fil-C, the language itself isn't the problem, since the runtime provides memory safety. I think that any post discussing memory safety in C and C++ these days needs to discuss Fil-C.
In my opinion Fil-C is one of the coolest projects in systems software in recent years. Βut if we're talking about greenfield projects in C and C++ then I have a hard time seeing how Fil-C fits. The two systems languages I still reach for willingly are C and Rust. I reach for C when I don't care about memory safety or about the correctness gains from having a stronger type system; I'm indulging myself as a lifelong C lover. (I've also used C++ in a professional setting my whole career, but I would never use that language willingly.)
For that use case Fil-C has been a great addition to my usual suite of debugging tools, especially since I'm a dependency minimalist when I'm writing C (or Rust), so the fact that Fil-C needs transitive dependencies to also be built with Fil-C has been less of an issue. But the code still runs way too slow to be deployed in that form; I'm sure that part will get somewhat better over time, but it's hard to see it becoming competitive with Rust or unsafe C in performance. That leaves us in a weird place where, in Fil's own words from a recent talk, "Use Rust if you care about performance; use Fil-C if you care about safety [by Fil's preferred definition of safety]". I only use C when I care about performance and when I do not care about safety!