I am solidly in the camp that believes C++ is unsafe. With enough discipline and tooling, it is possible to write safe C++. Are there more than a sliver of shops jumping through those hoops?
Of course, there may be a survivorship bias involved that proves me wrong. If it turned out that every remaining C++ shop is great at writing C++ code, because all the shops that weren't gave up and migrated to something else, I wouldn't be shocked.
I think it's possible to write "safe enough" C++ for real-world use. Then after some time you get a weird crash because MSVC stdlib implementation does something weird in new spec. Or your dependency does something unsafe and hoses you. Or the new guy uses `std::string_view` but forgot it doesn't guarantee null-termination. Or you casually forget about iterator invalidation because you're tired and compiler doesn't help you. Or or or. I like C++, it's a nice language but after befriending Rust compiler, or seeing how awesome hot reload in C# is or live-coding a front-end in javascript or any other advantages of other languages I find the use-case for C++ shrinking daily. Really if the C++ ecosystem wasn't so damn rich with libraries, tools and more I doubt it would have as much backing today.
And it’s eminently understandable that Mozilla was looking at a codebase from that era and said: “it’s time to consider drastic measures”.
But C++11 eventually got codified and adopted, clang-tidy and the sanitizers came on the scene, the screws got tightened yet further with 14, and 17, and 20 which is now substantially supported.
It’s just not black and white anymore: C++ has real interop/FFI and a largely “opt-in” mentality around the extreme low defect approach, Rust has a much more opinionated posture that’s trivially better at getting extreme low defect outcomes at shops more diverse in experience and rigor than old-school FAANG or Microsoft but botches some key stuff, there is not a one-size-fits all answer here.
Or rather there is if you don’t over constrain the solution space: if extreme low defect is the call you want a real type system. At the less mainstream end that’s probably Haskell or OCaml or something of that lineage.
But ultimately the Quality Inequality (good fast cheap pick two) remains binding: and TypeScript is just trivially where you find a lot of people who grew up with advanced dependent typing and don’t cost a million bucks a year each. It’s faster than most would guess, and the type system is up there with Idris or something like that in terms of the scope for machine checked rigor: these are MSR people, would anyone expect less?
The conversation is over-rotated on Rust vs. C++ which is a complicated question to which few need an answer.
My impression is this is not effectively true. It's not easy to follow all the rules of safe C++. I could be wrong but things like forgetting to use std::move in the right place or forgetting to use unique_ptr where you should. Putting member initializers in the wrong order. Forgetting a copy constructor or forgetting to hide the default, etc...
I'm also not sure of mapping structures to binary data (maybe this isn't enforced in any language but ...). Let's say you want to read a BMP file. You declare a struct that matches the header. If you declare every byte it might work but if you declare it as 16bit values and 32bit values then IIUC, padding rules extra make your code non-portable. What rule enforces that you don't write non-portable code? Also, what rule enforces no undefined behavior, like say int overflow?
The point isn't that it's possible. The point is it has to be enforced by the compiler. Does that option exist `--safe-cpp-only` or something like that the enforces 100% safe usage only? (and obviously, just like rust, you could break out when you need to?)
You unquestionably shouldn't use C++ or Rust, you should use WUFFS to write this portion of the software, this way you get the absolute safely you presumably expect and also you get much better performance than you'd get from hand rolling say C++. You will need to write extensive test suites since the WUFFS tooling doesn't know what a BMP is so your testing is the only way to know you're decoding it correctly - but the safety property drops out of the language design, and while performance isn't magically guaranteed it's much easier to write small fast code in a language designed to help you do that and which also catches every safety mistake you make in the attempt.
In practice, most C++ shops seem to limit themselves to a small subdialect of C++ that's reasonably safe. The C++ Core Guidelines are essentially an effort at canonicalizing some of these best practices and building them into the tooling.
I think it's fair to say that Rust or managed code should at least be considered for any greenfield project, maybe even a hard requirement for certain domains like cryptography, networking, financial applications, etc. But I don't believe that every application needs to be security hardened to that level, and the C++ library ecosystem is still much more robust than Rust's is. Crates.io feels like a graveyard of experimental libraries that reached 40% the functionality of their C++ cousins before the commit activity dropped to zero.
Not only that, but you see a huge increase in external dependencies because of the ease of importing crates. I'll admit I have absolutely no evidence to back this up, but the crates system feels to me like the Achille's Heel of Rust's security model in two respects:
1) There's the obvious supply-chain risk in that the provenance of most of these crates is...uncertain at best... At best you can see some stats that x number of projects use the crate, and who the owner is or at least purports to be.
2) Having eliminated most memory-related vulnerabilities (or at least constrained them to unsafe blocks,) the remaining vulns are going to tend to be logic flaws, and Rust, of course makes no guarantees in that realm. If you import 1 crate, ok, you can probably audit that crate and maybe reason accurately about what your code is really doing. But, when you import a half dozen crates, and those crates have dependencies, and so on, and you end up with 100+ external dependencies, I would argue that reasoning accurately about the behavior of your code is going to be quite difficult.
To me, this is a cultural problem/blindspot with Rust that will be difficult to fix.
This project's number one priority would be safety, followed by ergonomics. Performance would be somewhere down the list, below portability.
As an aside from safety, I'd like to whine about how the "modern" solution for organizing C++ code into modules was standardized ~4 years ago after however many years of development and still barely works in practice on any of the three major toolchains. What good is solving problems on paper if the solution is apparently near impossible to implement properly?
Moral discipline means rejecting the use of unsafe constructs for the sake of speed or convenience, and doing the safer, slower, sometimes more clumsy thing.
For instance, to skip thinking like "oh, we can just retain a direct pointer into here, and then we don't need to make a copy ...".
And, of course, while that is all well, you can only practice this advice only applies to small team or solo greenfield projects.
Where is he doing this? His main statement doesn't bear out this accusation: "“[t]here are two problems related to safety. Of the billions of lines of C++, few completely follow modern guidelines, and peoples’ notions of which aspects of safety are important differ. I and the C++ standard committee are trying to deal with that.”
I would. Almost all conversations about C/C++ security are, to this day, alive with people that are in my opinion delusional. The survivorship bias is more that the community is left with an over-representation of people that take “it’s often impractical for a team to write secure C/C++” as a personal attack against their intelligence and choose to dig their heels in as a result.
That is not safe either in practical terms.
So there is always this discussion about putting C++ as an unsafe thing and it depens a lot, as you said, on how you use it.
I use max warning level, warnings as errors, smart pointers, almost everything return by value and sanitizers.
In Rust I have the advantage that libraries can be audited for unsafe blocks, but it still has unsafe and it will still use unsafe libraries in practice from C.
So I always challenge these people that say Rust is safe. It is when it is. In rea life it is not perfect.
I am pretty sure that the distance gap in safety from well-written C++ and Rust is, well, very small.
But to say the gap in safety is very small is essentially to say Rust's lifetime system is close to zero-value.
Unsafe code still exists, the goal is to localize it to small, auditable blocks. However safety often depends on non-local properties. For example, unchecked iteration over a vector requires we know the vector's lifetime exceeds the iteration lifetime, and that the backing buffer won't change. Rust's lifetime system allows this to be expressed, so the "critical zone" can be localized within a single block in the library implementation. If the library is correct, all consumers are safe. In C++, these cannot be expressed, so the critical zone for this safety guarantee is smeared across all code executed during the iteration. No consumer is guaranteed safe.
All of these have runtime costs. What you describe is C++ forcing you to practice defensive programming in order to avoid costly debugging sessions.
Rust’s borrow checker lets you avoid some of that runtime cost. You can pass around references much of the time without wrapping them in smart pointers. You don’t have to reference count and don’t have to malloc and free as much. And while bounds checking will be on, you can avoid some of those sanitizers too.
You would get this benefit even if all code was unsafe in the libraries that you use, which of course it isn’t.
If only people were perfect, then things would be perfect.
There’s, what, 40 years of evidence to suggest that most people, most of the time, simply cannot write memory-safe C++ code (50 years if you count C.)
Maybe we should continue the experiment for another 50 years, just to be really sure the language is the problem.
That argument doesn't hold up too well, considering that people are involved either way.
The flipside, of course, is that language or tool problems have a much, much, larger blsst radius.
Still, you can twist yourself into knots to try to claim otherwise, but the track records of these tools speak for themselves.
Almost all modern cars are better.
You can do a lot in regards to memory safety also in C / C++ : https://llvm.org/pubs/2006-05-24-SAFECode-BoundsCheck.pdf
Anyway. Writing safe code in C is HARD but possible. Especially with good tooling. That is not to say everyone should use C. C is an exceptionally hard language to use safely and correctly and is not for everyone.
And in spite of my distaste for it, I respect that it completely eliminates giant classes of vulnerabilities. You can write bad logic in any language. At least in Java you can't write bad logic that also suffers from memory issues.
Oh you bet you can. There are a number of ways to screw that up.
- memory leaks/loitering is quite common, gc won’t help if your code hangs on to stale refs
- using Unsafe memory can blow up in glorious C++ fashion, directly or indirectly
- using native calls incorrectly can be just as nasty
C++ has bugs that bad that are found and weaponized daily.
So, Java is much safer probably by 2-3 orders of magnitude.
Also - the log4j thing shows just how dangerous class loading is. It’s an eval like mechanism. Probably future languages designed with safety in mind should avoid eval-like mechanisms as well as avoiding type system escape hatches.
Java's biggest mistake here, IMHO, was to have a serialization setup that was based solely on a generic interface rather than having the caller declare up front what class it expects to see. In the latter case, so long as you aren't storing naked Object/Serializable fields, type limits strongly restrict the set of naughty objects that can show up in your deserialized object graph. Rust Serde gets this right.
C++ barely made sense in 1995. It makes absolutely no sense today.
As someone working in a team of 3 with a c++ code base this statement is hilarious hyperbole.
This doesn't make any sense and nothing in this comment is something an experienced optimizer would say.
I'm not sure where the fantasy comes from that java is going to beat C++, but anyone experienced in optimization is going to control their memory allocations, then control data access being linear so the prefetcher works well, then control memory alignment, then worry about SIMD and multi-threading.
When people talk about optimizing in java, it's usually about turning off the garbage collection, fighting with the garbage collection etc. Memory allocation is trivial in C++ because so much ends up on the stack and preallocating memory is trivial. Java optimization gets stuck on step 1, trying to control memory allocations and pointer chasing.
Show me a program in java and I will show you how it can run faster in C++ (and possibly even faster in ISPC).
C++ is like a mad hatter's bad acid trip and somehow people are convinced it's still a great language to use in 2024.
On the other hand Herb Sutter recently wrote an interesting and grounded article on his views of Safety. While I don't agree 100% with him, at least he acknowledges the position C++ is in.
Edit: The article:
C++ problem is not helped by the compilers either, shoutouts to msvc++ accepting absolutely wrong and horrible code by default.
Then it wouldn't be C.
C is 52 years old. For most use cases, there are better options.
Instead of trying to apply bandages to C or C++, why not move to something better?
Legacy code written in C or C++ can be maintained or rewritten as needed. New code should not be written in C or C++ because it is not safe--thus the White House warning.
https://github.com/Cloudef/zig-budoux/blob/master/src/c.zig#...
C++ safety may have improved a lot, but it’s still far, far behind most other languages we use. It’s not even a close comparison.
You throw a bunch of programmers at a problem and you will get some number of bugs in the code. In C++, some percentage of those bugs will be memory errors. You can eliminate raw pointers but that doesn’t solve the problem—there are all sorts of places that dangling pointers crop up anyways, like in references that get captured in lambdas which get stored somewhere and then the programmer doesn’t realize that the lambda is called after the object is destroyed.
I’ve seen various solutions proposed to these problems. The worst solution is to “just hire better programmers and be careful”. The easiest solution for greenfield projects, most of the time, is to pick a different language.
basic_string_view is new in C++17. It sure beats pointers, but it’s a far cry from the kind of safety that you get in essentially any other language (except C): it is a reference with unknown lifetime, and the toolchain does not help track that lifetime.
I think that Stroustrup would say that the C++ Core Guidelines fix this, and I think he’s referring to this:
https://github.com/isocpp/CppCoreGuidelines/blob/master/docs...
which seems like it’s maybe partly implemented in some version of Visual Studio and was maybe prototyped in clang. But it does not seem to be a well-specified language or a fully-implemented language, and I can’t use it now.
So, as far as I’m concerned, I can use Rust or Python or Perl or Go or Swift or bash or Java or JavaScript or Haskell or Lisp or Scheme or O’Caml or Tcl and I can manipulate strings without worrying about undefined behavior. Or I can use C++ and worry. Or I can dream about using “Modern C++?”
I a decade of writing C++ I never experienced dangling pointer bugs _until_ I started using string_view. Using it safely in multi-threaded environments is damn near impossible. I now don't let them escape the enclosing scope, and if I have to then I make a std::string from it.
The first step to solving a problem is accepting reality. Cpp has been foundational, like C, to our computing world. If the rich legacy of libraries that underpin our "better" language choices is offensive to us, or if we really believe we are powerless to improve the situation around the most popular programming language(s) then we're not being realistic.
This is a huge debate of national importance and it'll shape programming language design for decades, it's important to get this right, and that will take more than just once choice and more than one approach to get right.
Ok, the first thing I’d like to accept is that C++ is just not safe enough for most applications. And yes—you also can’t throw C++ in the garbage.
Both of those statement are part of our reality—C++ is unsafe, and we will use it anyway. That’s why we solve this problem on two fronts. First, we advise programmers to ditch C++ for safer languages, when reasonable, because C++ isn’t safe enough for most needs. Second, we invest a lot of energy into making C++ safer, with better tools, safer libraries, changes to compilers, static analysis, run-time instrumentation, etc. It won’t close the gap—C++ is still unsafe and will be for the foreseeable future—but it will make a big difference to the people who, for whatever reasons, still use C++ despite its safety problems.
The C++ FAQ has a better picture here:
https://isocpp.org/wiki/faq/big-picture
> In 99% of the cases, programming language selection is dominated by business considerations, not by technical considerations. Things that really end up mattering are things like availability of a programming environment for the development machine, availability of runtime environment(s) for the deployment machine(s), licensing/legal issues of the runtime and/or development environments, availability of trained developers, availability of consulting services, and corporate culture/politics. These business considerations generally play a much greater role than compile time performance, runtime performance, static vs. dynamic typing, static vs. dynamic binding, etc.
There are a lot of good reasons to use C++. C++ is also unsafe. Both are true.
We don’t need to write long apologia explaining why C++ is actually safe, and you shouldn’t be looking at legacy code, or you need to hire better programmers, or you’re using the wrong tools or wrong practices or something like that. Ultimately, those arguments don’t withstand scrutiny.
Use Python. If you need concurrency, then use Go. If you need even more performance, use Rust (using unsafe Rust only for the parts that need it). For the highest performance stuff, maybe consider C for critical parts only.
C++ is not safe. It's a minefield of things that compile but are memory management mistakes. And then you're like "Look, I have a map of the minefield. If we just make sure we don't step on any mines, we are completely fine."
and a core part of getting this right is the maturity of other features, like generics/templates, duck typing instead of or in addition to inheritance, portable and well defined data memory layout, ease of reuse of existing mature C++ libraries (think numerical Fortran). I consider a language defined module system, straightforward parsing and higher level concurrency (like channels) almost icing on the cake...
nobody is served with a forced rush from one idiosyncratic complexity to another.
and imnsho the core issue with C++ is _not_ "safety" but complexity, of the language as such. it's just way to easy to write leaky or out of bounds writing code, in C and still in C++.
and in that vein I really love carbon and Herb Sutter's Work and get me right, also rust and even good old Ada got a lot right.
but please, can we discuss the better, next gen systems language as if it were not religions? but technology choices with ups and downs?
The fundamental problem is they’re just guidelines and they’ll always be just guidelines. You can still do all the wild old stuff without so much as a warning and you’ll have to figure out how it even interacts with the new stuff which exposes yet another vector for failure.
It’s possible. There just aren’t good incentives in place to do it.
Turns out what is more fun is to create a new language and rewrite the whole ecosystem. So a lot of the energy is going there instead.
Programming is part fashion...
Hasn’t stopped me from trying though. :-)
And the CHERI folks are trying, too.
So it’s really more of a political problem than a technical one. The excuse extravaganza that we’re seeing from Stroustrup and Sutter doesn’t help at all.
For me, the perfect C++ replacement would be something like Go without the runtime burden. I'm not sure that this exists, so I use Go whenever I can and C++ in the ultra rare cases where I can't.
• an architectural limitation of JavaScript which Rust doesn't have (in Rust you can wait for an async result in a sync function, or run CPU-heavy code from an async function).
• a wish that languages had implicit magic that made sync and async calls look the same, which Rust intentionally doesn't want, because implicit magic is a terrible footgun in low-level code. It can be hidden in a high-level VM language that is in charge of all I/O, syscalls, and locks. But that is counter-productive for a low-level systems language for implementing I/O drivers, kernel syscalls, and custom locking primitives.
So Rust is "purple".
In reality Rust's async is awesome for what it is: a syntax sugar for state machines, which is able to flatten an entire call tree into a single fixed-size struct.
Most other async architectures need at least one allocation per async call or per await, but Rust needs one allocation per the entire call graph with any number of async calls and awaits.
There might be a reasonable discussion here if we were discussing profiles when the earliest incarnations of it appeared around 2015, but we're not. It's 2024 and they're still not in the standard. There isn't even a clear proposal for compilers to begin implementing. Once there is, profiles will still be an optional, partial, and incremental solution at best. They won't even fulfill Stroustrup's stated desires to address all kinds of safety (for which there hasn't even been discussion yet).
> The white house is not some radical pioneer at the frontier of programming language design. By the time it says anything on the subject, it's been obvious to everyone else for years.
That we should return to Ada? ;p Oh, wait, sorry; I have no idea what that is. We need Rust.
Ironically I think Stroupstrup is actually doing more harm than good to his reputation by "evolving" C++ than simply putting it in maintenance mode and contributing to a modern language.
Strousoup: The future is a yet to be defined profiles.
Sutter: The future is a yet to be defined C++v2 that's backwards compatible but also solves the problems.
Chandler: C++ compile times are too slow. Going to build a brand new front-end that really fixes compile performance and maybe fixes memory safety & let's you call C++ code.
My take:
Re Strousoup: From what I've seen, Strousoup's ideas don't seem particularly extra likely to make it to the final standard and that's when he already has a formal standard written up. Prognosis: Modules were much simpler (conceptually at least), took ~6 years, and even 4 years after coming out have seen minimal adoption. This definitely isn't on track for C++26 and likely will encounter real-world roadblocks by C++30. If everything goes well, the industry would be ready to start adopting profiles in ~10 years. Or they could start using Rust today. Still quite vague in terms of whether or not this idea can be implemented by compiler authors.
Re C++v2: Vague hand-waving without a real plan on how to be meaningfully competitive with Rust. Since it's an experiment and experiments can fail, unlikely to lead anywhere and unlikely to see industry adoption.
Re Carbon: interesting experiment and the most likely real-world candidate to displace C++. Being done by a company with a massive C++ codebase that would benefit from migration to Carbon. From the looks of it, they're spending most of their time on compile performance. Neat technical experiment but not really showing that a language that's bidirectionally compatible with C++ can meaningfully improve on safety. Also, Rust compile times have improved significantly in the past few years and are better than C++ in my experience (but of course, hard to have a fair comparison of the two and I'm compiling these days on a much faster machine than I've used for C++ in the past). The recent Cranelift work might reasonably speed things up another ~2-3x.
EDIT: TLDR: C++ today is not safe enough and no idea when it will be, regardless of the work going on to try to make it safer with no idea when that might happen.
for C++ especially it's really two things that make it hard: it's idiosyncracies (that's what they work on) and then in addition the lack of these new abilities.
Bjarne Stroustrup 2018: We are on a path to disaster though enthusiasm and design-by-committee (or rather “design-by-committees”). During the early days of WG21 the story of the Vasa was popular as warning against overelaboration (from 1992):
“Please also understand that there are dozens of reasonable extensions and changes being proposed. If every extension that is reasonably well-defined, clean and general, and would make life easier for a couple of hundred or couple of thousand C++ programmers were accepted, the language would more than double in size. We do not think this would be an advantage to the C++ community.”
“We often remind ourselves of the good ship Vasa. It was to be the pride of the Swedish navy and was built to be the biggest and most beautiful battleship ever. Unfortunately, to accommodate enough statues and guns it underwent major redesigns and extension during construction. The result was that it only made it half way across Stockholm harbor before a gust of wind blew it over and it sank killing about 50 people.”
“It has been raised and you can now see it in a museum in Stockholm. It is a beauty to behold - far more beautiful at the time than its unextended first design and far more beautiful today than if it had suffered the usual fate of a 17th century battle ship -- but that is no consolation to its designer, builders, and intended users.”
3..2..1
Stroustrup is being tone deaf or maybe he just doesn’t get it.
The issue is that there are so many ways in which you could write a C++ expression that violates memory safety and gives users control of your heap.
In Java or other truly safe languages, there are zero ways to do that short of pwning the JVM with a bug. In Rust and other safe systems languages, to do something unsafe you have to call it out using the unsafe keyword.
So - the places in your C++ code where you might have a memory safety violation are everywhere while in the alternatives they are either nowhere or they are carefully demarcated.
“There are two problems related to safety. Of the billions of lines of C++, few completely follow modern guidelines, and peoples’ notions of which aspects of safety are important differ. I and the C++ standard committee are trying to deal with that,”
sounds like an admission that, following decades of improvements and modernisations to C++, safety and quality remain a practical concern in most actual C++ codebases. In many ways it’s surprising that it has taken this long to call time on it. I can understand Stroustrup’s frustration; the work they’ve been doing has been excellent, but there’s nothing stopping industry or the government switching to other options that are making better headway against problems like this.
This rhetoric from Stroustrup comes off as disingenuous; what saves it from being outright dishonest is the wording "an aim". As in one of many. Not "the aim".
Firstly, of course we get a jump in safety from K&RC to C++.
But most of the development of C++ has been driven by a hedge between multiple factors, only one of those being safety, and not always having the top priority. Factors like: ease of implementation, performance, safety and backward compatibility.
We can point to recently introduced library features that are not safe, and easily identify backward compatibility issues that prevent improvements in safety.
In the 1990's, C++ introduced a standard library of containers. If safety had been the top priority, iterators would never have had undefined behavior when the target object changes, and std::vector would have been impervious to out-of-bounds accesses. These things could easily have been achieved in what is just library code. A C++ developer can easily ignore the library, and develop their own containers with safe iterators, vectors that can't be misused and other elements. The standard didn't do that because of those other factors: ease of implementation and performance.
(Can someone point to three situations in the development of C++ in which safety ran up against efficiency or implementation ease, and did not lose?)