I currently work in a mixed C++ & Rust shop -- embedded Linux, autonomy systems for tractors -- and know C++ very well. I choose to work exclusively in Rust.
1. 10-20 years of C++ depending on your definition. But used C++ part-time casually/open-source from mid-90s to 2012 or so, and then mostly full-time from that point on @ Google, with a big chunk of that working in the Chromium source tree.
2ish years Rust, including the last year or so fulltime professionally.
2. Rust. Because of the more expressive type system, simplified/cleaner tooling, consistent syntax and style. The safety features are nice, too. C++ projects at work tend to get wrapped in Rust for the project I work on.
3. Rust. Same reasons as above.
4. C++'s const generics and constexpr support is much richer and better than Rust's. Placement new and custom allocator support in the STL has no answer in Rust yet, and the efforts to fix that (allocator_api etc) seem terminally stalled. Same with really good cross platform simd library support. C++ has a better story generally on embedded devices still, in terms of sheer # of toolchains.
5. I am not sure why people are focused on the borrow checker and safety as somehow the only distinguishing feature -- it's really not. C++ is missing a whole boatload of things from Rust, but most notably would be ADTs/sum types/pattern matching. This is the "secret weapon" of all languages inspired by the ML-series functional languages (StandardML, OCaml, etc.) and something C++ has no real answer for, though you can "sort of" approximate it with some template metaprogramming wanking I guess. Rust doesn't do this as well as ML or Haskell or F# or Scala because it confines it to its enums, but it's still really lovely and makes programs far more expressive and readable.
Not nearly as expressively though. You can destructure a struct, but you can't express alternatives on anything but an enum. Languages like F#, Scala, Haskell support more ways of creating ADTs than the kind of enums that Rust has.
2) C++, my employer's choice. Their rationale: easier to hire people with experience, internal codebase is mostly C++ so tooling/people more versed.
3) Rust. For me it's just easier overall to work with. It takes me an hour or so to get my head back into Rust's standard library and idioms/quirks, but afterwards it feels much better to work with: dev. workflow, testing, expressiveness. Plus it catches programming errors for me every once in a while.
4) I wish Rust had wider adoption by the industry. It's always compared to C/C++/Java/Go and the usual argument boil down to "not enough people use it".
5) I wish C++ had better compiler error messages. I have to rely on instinct when it's not scrolling through nested template/SFINAE errors to understand what's happening. Rust tells me "here's what's wrong" 99% of the time.
1. Hard to count. Started as a youth on game modding, but in terms of serious projects, probably 5-7 years.
2. Rust. It's a Rust shop, and the only C++ we have is in dependencies that we wrap with Rust interfaces.
3. Rust. I've probably shaved a not-inconsiderable amount of time off my life in debugging C++ issues at both compile-time and runtime (inscrutable behaviours, memory safety, broken / unstandardized tooling, platform-specific nonsense, template metaprogramming sins that I've had to debug and - worse - add to, and much more)
4. Hmm. Template specialisation and more constant-time evaluation, I think. There's probably a few others, but that's what comes to mind.
5. Uh. Pretty much everything?
- Consistent tooling that works across all platforms (I will be happy if I never have to look at a line of CMake ever again)
- Built-in dependency management
- A robust engineering culture (your code should account for failure!)
- Consistency of code itself, made possible through rustfmt and clippy
- Generics that surface issues at point of definition and not at point of instantiation
- ADTs
- Reduced reliance on human strictness / "getting it right" to, well, get it right. This isn't just the borrow checker - most APIs in Rust are designed to make using them wrong difficult. Even things like `Mutex<T>`, which combines a mutex and value, to make sure you can't access the value without locking, and you can't lock without knowing what you're locking.
- Easy multithreading through invariants that the compiler tracks (the Send+Sync traits); they're not perfect, but there's nothing like the first time you change an `iter` to Rayon's `par_iter` and your code is magically eight times faster.
- A value-based typed rich error handling scheme, so that you can see what errors a given piece of code might produce and be able to handle and propagate them with little fuss. (Similar to checked exceptions, but much more convenient! Adding some context to an error is a function call and `?` away, not a per-statement try-catch-rethrow).
---
Honestly, I could keep going for a while, but my general point is that Rust has had the ability to learn from its predecessors, and it shows. Many of the mistakes, bad ideas, or poorly-fitting features of past languages just aren't present in Rust because the language was deliberately designed to avoid them.
It's not a perfect language by any means - async Rust is the first and most obvious pain point - but it solves my problems in a much more ergonomic - and dare I say it, more fun - way than C++ ever did.
Well, they already did [0]. It's not very fun to use; something that was less miserable would probably see more use. Retrofitting them to a language isn't impossible, either; there are a few languages that have managed to do it by adapting to their ecosystem's mores, like TypeScript and Kotlin.
That beings aid, I don't disagree with your general point. C++ is already incredibly complex and impossible to understand as a single individual; I don't think that can be fixed without removing things from the language.
My gut feeling is that the future of C++ is not C++, it's cpp2 or Carbon.
> 1. How many years of C++ programming do you have under your belt? How many years of Rust?
12-15 years of C++, 8 years of Rust. I generally used C++ because it was the only tool that could do the job at the time, not because I actually liked it. At best, I had a love/hate relationship with C++.
> 2. For new work projects, do you choose C++ or Rust? Why?
Almost always Rust. The main reasons:
- Cargo is awesome, and the crate ecosystem is incredible.
- I barely trust myself to write rigorously correct C++, despite having plenty of C++ experience and being extremely paranoid about code correctness. And I defintely don't trust most other people to do it correctly. (I have maintained more than my fair share of other people's C++ code, and the average C++ code is buggy garbage.) C++ is like fishing around in a drawer of extremely sharp knives in a dark kitchen; you're going to get cut.
- I trust the average Python or TypeScript developer either to write correct Rust, or to be defeated by the borrow checker. This isn't just the borrow checker—it's also bounds checking on arrays and slices, the choice to panic instead of corrupting memory, the rareness of undefined behavior (and "nasal demons", and irresponsibly aggressive optimizations) in safe Rust, the safe mulithreading support, etc.
However, I would consider using C++ in domains where correctness and security weren't important, and where I wanted to use mature frameworks. Games would be an obvious case!
> 3. For new hobby projects, do you choose C++ or Rust? Why?
Rust. Way more fun, personally. :-) I tend to like using "functional" architectures, so I don't fight the borrow checker much.
> 4. Is there something about C++ that you wish Rust had?
Well, now that const generics are in, I'm pretty happy with Rust's feature set. But for quite a while that was the best C++ feature Rust was missing.
1. About 15 years of experience in C++, 2-3 years in Rust.
2. I'm retired now, but I would have considered Rust for any greenfield project that didn't have a compelling reason to pick C++ (e.g. mandatory interop with an existing big C++ library).
3. Rust nearly every time. I sometimes use a layer of C++ as an FFI shim, such as when writing a gRPC client/server (the Rust libs there aren't mature).
4. The library ecosystem. Rust's crates.io seems full, but if you scratch the surface it's just a bunch of half-baked hobby projects at v0.x versions. And Cargo (the most popular build system for Rust) has essentially no support for multi-library packages, so instead of something like GLib (a single dep with lots of functionality) it's common to see even trivial programs depending on dozens or hundreds of half-baked hobby projects at v0.x versions.
5. Most of the Haskell/ML-ish functionality, such as traits and proper enums. I'm currently in the middle of a Rust project that's gotten much bigger than I expected (parsers are hard! compilers are hard! zero-copy is hard!), but it's still possible to make progress by leaning heavily on the type system.
I often describe Rust as "a dialect of C++ where the compiler forces Google Standard C++ on the world", or alternatively "the language you'd get if you hired a team of Haskell developers to write firmware".
Your thoughts about crates.io mirrors mine exactly. I'm becoming more and more interested in just ditching it entirely and using vendored checked-in (or submoduled) dependencies and managing the transitive dependency tree myself. Cargo&crates.io seems to have been inspired too much by npm. Lots of half-baked, abandoned libs that wantonly pull-in piles of other 3rd party deps and create a sprawling graph of dependencies that ends up with binary bloat and multiple versions of the same package, etc. etc.
somewhat OT, but what happened to haskell? a few years ago it was all over HN - now you hardly see any mention of it. surely this won't happen to rust?
As far as I remember, most of the pillars of Haskell were hired to work on projects that were, at some level, competing with Haskell (by Microsoft, Facebook, Epic).
Entirely different snack bracket. Haskell is great stuff, but not something you'll be able to practically staff a team and build with.
Rust is not nearly so "brainy." It's a much more pragmatic and conservative language.
And it's targeted in an entirely different domain -- systems programming. Haskell is a garbage collected pure functional programming language. Rust is not anything like that.
> And it's targeted in an entirely different domain -- systems programming.
> Haskell is a garbage collected pure functional programming language.
At the time there were many people trying to make a go of doing systems programming in Haskell. I remember lots of arguments about libraries using ByteString/Text vs String in their public APIs, where the research people were like "linked list of u32 is so conceptually pure!" and the systems people were like "what, no".
In retrospect, starting with a low-level systems language and adding safety turned out to be a better idea than using `ST a` to implement borrow-checking in a high-level GC'd hosted language. I can only say "it seemed like a good idea at the time...".
There was an era of stability in the Haskell project during which it was possible to write code for industrial purposes. A lot of people were attracted to the idea of a memory-safe language that compiled to native binaries, and there wasn't much competition in that area at the time.
Then the research-oriented nature of the language and community re-asserted itself with Haskell 2010, and there was a lot of churn in both the base libraries and Hackage. Around this time was when Swift, Go, and Rust had their first releases, all of which offered memory safety and varying levels of ML/Haskell inspiration. So Haskell was suddenly less appealing, and there were other options -- why file another perf-regression GHC ticket into the void when Go has an HTTP server built right into the stdlib?
I don't think it's got anything to do with Haskell 2010. Haskell is far more widely used in industry today than in 2010 by at least an order of magnitude. Heck, Mercury alone has about 250 Haskell programmers I think. That's probably more industry Haskell programmers than there were at all in 2010.
1. I had about 7 years of professional C++ experience, a few more as a hobbyist. By now, I have ~10 years of Rust.
2. I would definitely choose Rust. For better or for worse, the teams I work with tend to consider developer velocity as their highest metric. Between cargo, clippy, crates and the type system, I'm orders of magnitude more productive in Rust.
3. Same thing. My life is too short to spend it debugging memory or concurrency errors.
4. There are a number of C++ libraries that have no equivalent or good bindings in Rust yet. But if you're talking of the language, no. There are C++ features that do not have an equivalent in Rust but I don't miss them.
5. From the top of my head, affine types (e.g. after a `std::move`, the type system ensures that you can't use the value anymore), proper enums, proper pattern-matching, better concurrency operations in the standard library (which pretty much requires affine types), a linter as good as clippy, built-in support for writing new linters/gradual type systems, derive macros (that have access to the AST).
I agree. I'm using it both in the game server, the client and for scripting! So far I haven't really had any serious problems that would be related to unsafety. You just have to be disciplined. Source: https://medium.com/@fwsgonzo/using-c-as-a-scripting-language...
I do see that peoples experience with C++ varies wildly, because shops either don't understand why modern C++ exists or they don't care. Either way, predictably, their software suffers and they will routinely run into segfaults. C++ obviously isn't perfect. It's an old language, and while it's trying to reinvent itself it will take some time.
If you ask me though, I don't give a rats ass about the safety things. My $dayjob is a big infrastructure product written in C, which practically never crashes because of memory problems. It just doesn't happen because of extreme discipline. Asserts when entering. Asserts when leaving. Magic values in structs. Sanitizers, clang-tidy, coverage, custom test frameworks, fuzzing. We have it all. And it's C! I understand newbies do better with Rust than C++. It makes a lot of sense. Still, I think that people use Rust more because it's easy to add dependencies, because it has 1 (one) build system, and because of that it's easy to build larger projects because of this. CMake has made good strides recently, but it needs to become the build system for C and C++, and we need global repositories to match Rust. Will that ever happen?
Speak for yourself: I'm not returning to the world of "discipline" (aka lost productivity) without a damn good reason to carry on existing codebases. It's just a waste of a good coder and certainly of good time.
I completely disagree; discipline is only optional in C++ if you want to ship broken code. Even the top poster in this thread that actually cops to the productivity overhead of C++ admits (albeit obliquely) they still make memory errors
I think this is amusing because both directions completely work:
In C++, you can do whatever. Rust forces you do to some things. No discipline required by C++, absolutely required by Rust.
In C++, you can do whatever. Rust forces you to do some things. This means you must have discipline in C++, or you will do bad things. But in Rust, you can do whatever you want, the compiler has your back.
It sounds like you're working on a mature C++ codebase, so yeah, good reason to stick with that. But I'm curious whether you feel it's worth spending effort on this discipline, outside of working on a particular codebase?
I don't know C++, but being freed from making effort to avoid a class of bugs was easily enough for me to learn Rust instead (not that this was the only reason).
I think if I had the opportunity to learn Rust, and some guidance so that I didn't have to do everything the hard way (essentially how I learned C and then C++), then I would!
So far I have used Rust very little, but what I have seen I have liked. I especially liked the much simpler inline assembly in Rust.
That's a lot of boilerplate code, scaffolding and additional instrumentation. In fact, much of my experience with C++ is at Mozilla, which does all of that and more. And still needs a team dedicated to recording and investigating crashes.
That's the reason for which Mozilla (and now some of the developments at Google, Facebook, Microsoft, Amazon) has started moving to Rust a while ago.
Also, I've written it somewhere else, but I'm going to repeat it:
> which practically never crashes because of memory problems
When you have memory issues, crashes are the lucky case. The common case is random memory corruption.
A few things I've found notoriously difficult to do in C++:
1. Add two signed 64-bit integers without overflowing on either side (negative or positive) and without using 128-bit integers. If the sum is going to overflow, generate an error. If the sum is not going to overflow, evaluate the sum.
2. Multiply two signed 64-bit integers without overflowing on either side (negative or positive) and without using 128-bit integers. If the product is going to overflow, generate an error. If the product is not going to overflow, evaluate the product.
Essentially under no circumstance evaluate something that might overflow. Detect the overflow and generate error. Evaluate only if the detection algorithm says there is not going to be overflow.
We can't use 128-bit integers ecause some hardware may not have a 128-bit registers. It's probably possible to solve these but it gets complex pretty soon once you begin handling all the failure modes.
Does Rust make this kind of problems easy to be solved?
When you have wrappers over these that produce optional types, and use statement expression macros or monadic member functions to handle the control flow, I think it's not too hard.
Rust has i64::checked_add and i64::checked_mul, which return None on an overflow. Not only is it easy to do, the compiler help ensure you checked the result.
> Does Rust make this kind of problems easy to be solved?
For this specific case (specified behavior on overflow), Rust makes it trivial. All of the primitive integer types have methods such as `checked_mul()` or `saturating_add()`, which provide deterministic arithmetic on all platforms.
Rust often has convenience methods in its standard library for things like this that are common and tend to produce bugs when re-implemented over and over, and have a reasonable way to do it without making tradeoffs that will annoy some people over others.
In this case, it’s:
10i64.checked_add(20i64)
10i64.checked_mul(20i64)
Both expressions return an Option type, which is effectively a tagged union with type safety on top so you can’t misuse it. You can either call “unwrap” to get the value out and trigger stack unwinding on error, or pattern match on the Option and its inner value in a more functional style.
Option<T> being the Rust equivalent for std::optional, but with all the nice ADT features so it's ergonomic to use. You can transform it to a Result<T, E> if you want, or even just panic and exit if it overflows. In any case, the overflow handling is built-in and doesn't use 128 bit ints.
> 2. Multiply two signed 64 bit ints without overflow
I'd wager that any highly qualified C++ engineer worth working with can pick up Rust and become a highly qualified Rust engineer in <6 months working in it full time.
And honestly, I'd rather work with those people than with people who came to Rust from languages like Python, Java, TypeScript, etc.
I picked up Rust in a few days/weeks, but ... I'd say not totally idiomatically. It actually takes a while to really learn the ins-and-outs of the right way to express certain patterns.
If the codebase that the eng is being brought into is established with good conventions, I could see it happening quickly though.
I've seen engineers with prior C++ background and barely any knowledge of Rust brought in to work on a well established and large Rust codebase with coding guidelines and the such, and they were rocking in less than a month.
Why? The place I work at decided to use typescript for their integration testing framework for some reason. Everything else is c++ and python, and the developers they hire all know c++ and python, but not typescript. As a result, the integration test framework is a dumpster fire.
What kind of strawman game are you trying to play? Those are not my words.
Cargo is... ok. It has rough edges, but mostly works. Various things frustrate me, though there's usually work arounds. Workspace support is still a bit half-assed -- I mean, take a look at this terminally open but rather critical issue: https://github.com/rust-lang/cargo/issues/3946 ; our $work source tree is littered with brittle hardcoded relative paths because of this basic missing feature.
Crates.io is ... not ok. It's a wild west full of hobby and abandoned projects with undisciplined massive dependency trees, has people camping out on names, and lacks even the most basic checks and balances (properly moderated submission process, expiry) and features (org namespaces) that Maven had back in like 2007.
Because of this, incredible discipline is required on large projects with large sets of third party deps or you will end up with multiple versions of things linked into your binary, or with abandoned projects in your dep tree, or just dep bloat generally.
And this ties back to cargo, too. It was only with last week's 1.74.0 release that we finally got the ability to do authenticated repositories? Again, something that Maven supported from day 1.
It's a bad culture that was, I think, imported from the node/npm world. If I were starting a serious corporate project from scratch in 2023, I'd seriously consider a model with checked-in, vendored, moderated dependencies that doesn't use crates.io (or a mirror) at all.
In practice, there are only like, three-ish build systems in C++, and they all work better than cargo. There are a handful of issues cargo could get around to fixing that would help dramatically, but they always seem like they would rather spend hours arguing in the comments to bikeshed a patch rather than deign to type even a single line of code themselves, so they burn out all of their contributors and end up with critical fixes stalled for literal years while projects pile on ridiculous workarounds that no one ever documents anywhere :/.
Rust is not competing against a stale competitor. C++ has evolved quite significantly. Competition is good and things are improving for everyone. That said, it’s not challenging to write modern c++ without coming across raw pointers or buffers.
That being said, porting existing codebases from "old C++" (say < 2011) to "modern C++" can be a herculean task. I could be wrong, but my impression is that only clean slate codebases benefit from such modernity.
And I know that I've jumped ship from C++ to Rust a while ago. Right now, I don't see any compelling reason to go back.
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’s not the same at all. Unsafe can be used pragmatically.
[Regarding UB in C++] 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…
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.
Talk about premature optimization. You’d rather have all of the pitfalls of Undefined Behavior to get an optimization you can achieve in Rust because you’d rather be less explicit?
This article is clearly not directed at me.
For the record, I love C++ and use it daily and will continue to do so. I also like Rust. It can be frustrating at times to have to change the way you think about structuring your program between the two, but that doesn’t mean that one or the other can’t be used effectively and safely.
> 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.
Is this true for most people though? What do you think about this quote?
Frankly I've seen a lot of segmentation fault issues with C++ projects. Granted much of it can be detected pre-emptively with good tools and when a segfault happens, I can debug it quickly too using a good debugger.
But I've to say that they still do happen and I still see them happening enough while developing and testing that I cannot simply dismiss segfaults as something unimportant.
As someone said above in the thread, segfaults is what you get if you're lucky. If you're unlucky, you get silent memory corruption that you will eventually notice through bugs popping up in seemingly unrelated parts of code.
The "The Dubious Benefits of Safety" is highly naive.
I think the author greatly underestimates the ease with which code ends up exposed to attackers. Sure, the author works on some internal thing. But where does the data come from? And data often comes in complex formats. Does the application deal with say, images or zip files? Lots of room for something to go wrong there. Projects that start internal can become part of something exposed to the outside. A modern, large project may have a few dozen dependencies, the quality of each is far from guaranteed.
For crashes, the issue is that in a complex enough project, troubeshooting is often far from trivial. Things often explode in a different place from where the bug was. My hobby project simply doesn't run under Valgrid, it's too slow for the code to run under it.
Being able to manage memory manually for certain performance-critical tasks is certainly reason enough that these types of languages will continue to exist.
But the idea that stack overflows are not an issue if you try hard enough and pay attention is not very persuasive. It doesn't take much imagination to see that this principle will falter more and more the larger a project gets and the longer it runs. This remains THE big selling point of Rust to my mind.
Yeah, that's a no from me. I'm a full time C++ dev and every time I code in Rust it is harder for me to switch back. The author has pointed to some issues regarding performance. In the end, you can always rewrite your code to generate other/better assembly, if you really need the speed. I would argue Rust is so much more that just Rust itself. Like many others after noted, it is not only the language, but the whole ecosystem and oh boy is cargo a joy to use. Yes, CMake works ok with vcpkg, but good luck integrating something that is not CMake. Code style, build script/system and different bugs from different compilers do not make my life easier. In addition, we still have old features we cannot repair because of ABI. It is an endless mess.
there is another key difference between rust and c++, rust by default bundles its stdlib into the executable, which make each executable kind of a static binary, they add up fast.
c++ uses shared libraries, smaller in size for the whole system, and, you can upgrade a library separately too.
on embedded systems where storage space is restricted, rust is simply a no-go.
[profile.release] # A profile to try to minimize the size
panic = "abort" # Abort on Panic
strip = true
opt-level = "z" # Optimize For Size
lto = true # Enable Link Time Optimization (LTO)
codegen-units = 1 # Reduce Parallel Code Generation Units to Increase Optimization
that's for MCU and yes, as it does not use rust's stdlib.
there are lots of embedded systems that run with glibc or musl shared libraries, if rust is used there, it will blow up the storage space with just a few executables due to the static stdlibs.
I don’t understand why techniques valid in small spaces aren’t also valid in slightly larger spaces.
If you don’t have the budget for the standard library, don’t use it. If you somehow have the budget for glibc and not the rust standard library, call into glibc instead of the rust standard library.
Yes, I understand what you're saying. I am saying that if you find yourself in that situation, don't use the Rust standard library. Your Rust programs can share glibc with your C programs just fine. This isn't a disqualifying situation for Rust. Heck, when you use the Rust standard library, dynamically linking to glibc is the norm!
of course I knew no_std, it's for MCUs, similar to c++'s nostdlib, I'm comparing rust's stdlib to libstdc++ though, but the former is not shared-lib friendly to say the least.
Flash storage is getting cheap. There is some places where it matters.... but on everything else.... I am really not a fan of dynamic linking.
Linux has proven for a very long time that if you have 100 programs, and they each have 100 dependencies, you'll probably see an occasional issue related to incompatibility.
It seems like it's suited to the mindset of unique systems maintained by sysadmins, with every app being a part of the system, quite possibly built locally just for your task.
The modern way seems to be disposable installs that cab be recreated automatically if some probably-not-worth-fixing thing happens, made of reusable multipurpose software made by people who have never heard of your application, and a bit of local code, which might be intended to someday become one of those mega-frameworks.
Even windows used to always have hassles with some visual C++ runtime or other.
Although the standard library of C isn't a big issue since it's not in the habit of breaking things, but I'd imagine Rust might want to make bigger changes since it's so new.
There's a safe and sane language subset struggling to get out of "modern C++". The essence is following the Rule of 3/5/0, adopting value semantics, explicit ownership via unique_ptr/shared_ptr, resources wrapped as RAII objects. The syntax and feature space is certainly crowded and abused, but playing with the borrow-checker is an entirely different game.
Yeah, I've tried to find this mythical subset. I handwaved my way around most of the difficulties, but I think that it exists. However, I'm not convinced that C++ developers are going to enjoy it. In particular, the subset I could come up with is much less expressive than Rust.
That's my problem when people talk of C++ as if it was a sane, consistent language with a common style and doctrine like C#. You could get anything between C with classes and turbo template metaprogramming header only Boost specials.
Job postings with C++ should probably include an example source file..
Late to the party, but: this unfortunately doesn't seem to be true. There's no subset of C++ (or C for that matter) which is both safe and practical to use. This is why, say, the Chromium folks are seriously looking at adopting Rust. Same goes for the Linux kernel and their adoption of Rust, although of course that's a C codebase.
To rework an old comment of mine: [0]
There is no subset of C++ which is both safe and practical for real use. The MISRA C++ subset isn't enough to provide solid assurance of the absence of undefined behaviour, for instance. For that, you need a full-bore formal verification system, akin to that of the SPARK Ada language.
This is in sharp contrast to Rust, where there really is a subset which is safe 'by construction' (called Safe Rust).
> Are we talking about safety against hackers? The large majority of C++ applications are non-public facing.
That is like saying that most screws aren't load bearing, so it's fine if they fail 30% of the time. The way engineering works is that people build on reusable pieces. For example, several bugs in libjpeg have caused serious vulnerabilities in many applications, including web browsers. You cannot predict how software will be used; it will end up somewhere in someone's stack and suddenly its bugs are CVEs with huge impact.
C++ programs do not fail 30% of the time though. Safety concerns matter when the program is actively being attacked, the reason libjpeg had these bugs in the first place was that it works just fine in absence of attacks so nobody bothered to find them. It would be nice if by switching to some magic language we've got safety at no cost but it's not the case in the reality.
No one ever said that safety was zero-cost. The article mentions bounds checks, but gives no measurements. Have they ever measured? Most data I've seen is that bounds checks cost less than 1% in real-world code. I will boldly assert that there has never been a product that couldn't be shipped because that 1% caused them to miss a performance goal. We could get into the weeds and go off on a tangent about linear algebra kernels and loop transformations being slower / more difficult with bounds checks, but that is an absolute niche case that can be solved with different techniques like loop versioning and static assertions.
>Most data I've seen is that bounds checks cost less than 1% in real-world code
So? I don't know where you got you data, but the point of the article is that you don't have to pay for the stuff you don't need. Otherwise we could as well use Java or VBA because "most data" says that programs spend 99% of time waiting on IO. If your data shows that then go ahead and use it, other people have other data apparently.
> the point of the article is that you don't have to pay for the stuff you don't need.
I kind of don't get this attitude, TBH. A bad register allocation decision could cost 5% in the wrong circumstance, yet no one is suggesting reintroducing the register keyword, and rarely do they drop down to asm to get it. But as soon as the cost is for "safety" they're hell-bent on getting rid of it. Wth.
> A bad register allocation decision could cost 5% in the wrong circumstance
5% of what?
>yet no one is suggesting reintroducing the register keyword
If I understood you, you mean that some variable might be allocated on stack instead of register and we need "register" keyword to alleviate this? Not really, when such thing happens we write asm() block which has all capabilities of the old register keyword and much more. And if 5% is latency then who in real time would not get to asm() to get it? This whole talk of "1% this, 5% that" seems just some kind of reddit meme. Would you give me 1% of your income for nothing? If not then why would you increase latency by 1%? Would you write five line of assembly to get 5% raise? If you would, then why would not you want to do it to drop latency by that much?
What is more important than "are bounds checks expensive or not" is that Rust gives you tools to do both, just like C++ gives you tools that do both. The defaults are flipped, but both languages are equally capable.
Dropbox famously found bounds checks to be expensive in one part of their Rust code, so they introduced the equivalent of a feature flag to toggle them.
> The article mentions bounds checks, but gives no measurements. Have they ever measured?
Related, the prominent Rust community member "Shnatsel" wrote an article [1] about exactly that in January. It's a great look at the problem, and gives some actual data to evaluate with.
Thanks for the reference. Several of the examples given could benefit from loop versioning and/or iteration space partitioning, i.e. the compiler splitting a loop that it cannot prove is always in-bounds to an in-bounds part and a possibly-out-of-bounds part. The HotSpot C2 compiler does this under some circumstances.
E.g. the simplest case:
for (i = 0; i < some_expr; i++) {
a[i] = other_expr; // cannot prove is in bounds
}
Becomes:
var t = some_expr;
if (t <= a.length) {
for (i = 0; i < t; i++) {
a[i] = other_expr; // no bounds check necessary
}
} else {
for (i = 0; i < a.length; i++) {
a[i] = other_expr; // no bounds check necessary
}
throw OutOfBoundsError; // throw exception at first OOB iteration
}
This approach will have exactly the same behavior as the original program and run without bounds checks in either case. It will even throw at the right time. The cost is having two copies of the loop and a check up-front. This specific case could be done with one version, and other cases can be done with a different partitioning of the loops.
I feel people are lacking imagination on what an awesome buffet of compiler transforms are available when it is both absolutely required to do bounds checks and absolutely necessary to try to eliminate them everywhere. In practice, many many loops are trivially in bounds, many other loops are cold enough or big enough that the cost of a bounds check isn't much. You don't need to unroll and version every loop in the program. And like I said, for the loops outside of this set, where iterations are not affine or the indexes are irregular, you either live with bounds checks or you go deeper on allowing static assertions to prove accesses are in bounds. That's exactly the cases where you probably want checks anyway.
As a Rust developer (and former C++ developer), I agree with much of what the author writes. I feel that Rust is a largely better language for my tasks but yes, Rust doesn't yet cover all the use cases for C++. I feel that the reasons to pick C++ for any new development are dwindling, but they still exist.
However, this author, like many, does not understand what safety means.
1. There is much more to safety than memory-safety. Safety is about protecting your invariants. A program that is not safe is a program in which things that happen that are contrary to the intent of the developer.
2. Memory-safety is just one of the many tools in the safety toolbox. Without memory-safety, it's really easy to shoot yourself in the foot. With memory-safety, it's still possible, but you have to work harder at it.
3. Segmentation faults or stack overflows are what happens if you break memory-safety and you are extremely lucky. In most cases, what you get is random memory corruption. Do you really want memory corruption in a trading platform?
I used to be very comfortable in C++... then I stepped away for ~7 yrs. Now I'm back and I loath it. I loath that there are so many ways to wrap a pointer. The built in ones like shared_ptr, unique_ptr, etc and then all the per-project ones. On opt of which there's now std::move etc and I always need to know when to use one vs the other and when to call move vs when not and all the rules related the project's own pointer types. It really really sucks and I'm never sure I'm not breaking some rule. I'm shocked that when I was more comfortable with C++ that I didn't notice this glaring issue in front of my face.
I'd much rather have a language that just "does the right thing" and or at least prevents me from doing the wrong thing. Not just leaves it as an exercise to the user to pray and hope they get it right.
I haven't had the opportunity to use rust yet but one thing I think I look forward to is a more standard way to deal with dependencies and standard built tools. languages that have a popular package manager have their issues with people using too many dependencies and possibly easier supply chain attacks but dang it's night to just get shit done and not have to futs with makefiles, CMake, Ninja, Scons, auto-config, XCode projects, Visual Studio projects, etc...
But given their high frequency trading industry, I secretly hope a competitor abuse his code base with a 2^30 priced bid or something and his company go bust.
> "Why I think C++ is still a desirable coding platform compared to Rust"
And yet, C++ remains an undesirable coding platform, for the reason alone that there is no package manager. It's difficult to take this blog seriously when there is not even a faint mention of Cargo.
But even beyond that, I have other gripes with other fundamental points of the author's case:
> 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.
Sorry, but it strikes me that the people arguing against Rust are C++ die-hards are married so strongly to status-quo that they couldn't possibly imagine a better future. Which is of course no different than die-hards in other languages.
This article reads like nonsense to me, but the last point of security is just blatantly ignoring facts.
> Are we talking about safety against hackers? The large majority of C++ applications are non-public facing.
Total nonsense. Even a PHP application will be running behind various webservers written in C/C++, the runtime is written in C, and there are plugins/libraries like libcurl also written in C/C++.
This really reads like someone who's never touched Rust outside of a 15-minute intro or something. The aversion to `unsafe` doesn't really make a whole lot of sense if your main focus is raw performance... that's why there's a big suite of unsafe operations that skip the safety checking steps.
> 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.
Which purpose? The idea is that the majority of code that people write is in safe-land, and the bits of unsafe you use are up to you to verify that you've used it correctly. Using `unsafe` doesn't "defeat the purpose" of Rust, it tells Rust to trust that you've done something correctly.
> Well, yes, if you are going to list the “safety” of Rust compared to C++ as a pro...
"Pro" =/= "never makes mistakes". Memory safety is hard. Mistakes happen. C++ will happily crash at runtime while Rust will catch roughly 100% of mistakes in safe code at compile-time. Writing unsafe code yourself is certainly hard, but chances are that the Rust community has already done the hard part for you and made a crate featuring the data structure you're looking for. At that point, there's very few memory-related foot-guns primed for you to shoot yourself with.
> 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.
"If you ignore the thing that lets the Rust code do the thing that you want it to, then the Rust code doesn't do the specific thing you want it to."
> C... was created with the use case of it being a low level language... C++ inherit that, which makes it arguably the easiest language to code memory-intensive algorithms and data structures like hash maps, btrees, vectors and lists.
nit: "C++ inherit_s_ that". Also, this just speaks as someone who hasn't been a junior in a very long time. The number of ways you can subtly fuck up memory access in C++ is astounding, so much so that people have spent hundreds of thousands of man-hours designing memory safe languages like... Rust...
> This means that a good developer who knows how to take advantage of cache/locality, will have a good time implementing such algorithms and data structure with C++ and will very likely struggle with Rust for the same task.
Define a "good developer" more specifically. A good Rust developer certainly knows how to handle unsafe code without shaking in their boots.
> Rust mandates the use of its own compiler - rustc - which is a top driver of the LLVM stack, pretty much as clang is for C++. C++ on the other hand, has dozens of good quality compilers available in many platforms.
C++ is a 38 year old language designed in a time when you'd be happy that you had a compiler at all, let alone one that worked properly. Having multiple compilers for a language isn't a necessarily good trait, it just means that more work was spent duplicating functionality. And despite that previous statement, Rust doesn't "mandate" you use any particular compiler; there are multiple projects actively targeting other backends like GCC and cranelift.
> Although the scenario is changing rapidly, the pool of engineers with C++ background is much larger than the pool of Rust developers
See "C++ is a 38 year old language"...
> 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 you... sure you're using C++ then? This statement comes across as both a humble-brag and also just patently false if you've done anything even remotely lower-level. Do...
> Also, the quantity of teaching material and resources available to learn the language is currently overwhelmingly more abundant on the C++ side.
Quality over quantity. I'm a predominantly C and C++ developer, and the generic resources for C and especially C++ are horrible for the better part. This changes though when you look at what actual experts are putting out, either by themselves or by presenting at some convention, etc..., but these resources take knowing where to look, and many of them are useless in the process of learning the essentials, which everybody will screw up, because the language doesn't guide you, unlike Rust, which nannies you a lot (thankfully!!! you understand if you've written enough C++.)
> So if I want to bet safe in the development of a new system, I have to go with C++.
despite what I said above, C++ is prone to getting you in very unique scenarios if you're actually doing a new system (my anecdote), so you can't rely on people who aren't already skillful to even figure out that there's issues, even less how to address them. There's no guidance, really (unless you use C++Insights and hack stuff to compile-time.)
> 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.
You espouse a pop understanding of software issues. These issues are not just magic, they happen for very exact reasons, this is why we can work against them happening in the first place.
I end my comment with saying that I'm not even a big fan of Rust, it's very specifically designed, I do not like the principles that Rust builds on, but I still disagree with your analysis.
109 comments
[ 3.8 ms ] story [ 190 ms ] thread1. How many years of C++ programming do you have under your belt? How many years of Rust?
2. For new work projects, do you choose C++ or Rust? Why?
3. For new hobby projects, do you choose C++ or Rust? Why?
4. Is there something about C++ that you wish Rust had?
5. Is there something nice about Rust (apart from the borrow checker) that you wish C++ had?
1. 10-20 years of C++ depending on your definition. But used C++ part-time casually/open-source from mid-90s to 2012 or so, and then mostly full-time from that point on @ Google, with a big chunk of that working in the Chromium source tree.
2ish years Rust, including the last year or so fulltime professionally.
2. Rust. Because of the more expressive type system, simplified/cleaner tooling, consistent syntax and style. The safety features are nice, too. C++ projects at work tend to get wrapped in Rust for the project I work on.
3. Rust. Same reasons as above.
4. C++'s const generics and constexpr support is much richer and better than Rust's. Placement new and custom allocator support in the STL has no answer in Rust yet, and the efforts to fix that (allocator_api etc) seem terminally stalled. Same with really good cross platform simd library support. C++ has a better story generally on embedded devices still, in terms of sheer # of toolchains.
5. I am not sure why people are focused on the borrow checker and safety as somehow the only distinguishing feature -- it's really not. C++ is missing a whole boatload of things from Rust, but most notably would be ADTs/sum types/pattern matching. This is the "secret weapon" of all languages inspired by the ML-series functional languages (StandardML, OCaml, etc.) and something C++ has no real answer for, though you can "sort of" approximate it with some template metaprogramming wanking I guess. Rust doesn't do this as well as ML or Haskell or F# or Scala because it confines it to its enums, but it's still really lovely and makes programs far more expressive and readable.
2) C++, my employer's choice. Their rationale: easier to hire people with experience, internal codebase is mostly C++ so tooling/people more versed.
3) Rust. For me it's just easier overall to work with. It takes me an hour or so to get my head back into Rust's standard library and idioms/quirks, but afterwards it feels much better to work with: dev. workflow, testing, expressiveness. Plus it catches programming errors for me every once in a while.
4) I wish Rust had wider adoption by the industry. It's always compared to C/C++/Java/Go and the usual argument boil down to "not enough people use it".
5) I wish C++ had better compiler error messages. I have to rely on instinct when it's not scrolling through nested template/SFINAE errors to understand what's happening. Rust tells me "here's what's wrong" 99% of the time.
2. Rust. It's a Rust shop, and the only C++ we have is in dependencies that we wrap with Rust interfaces.
3. Rust. I've probably shaved a not-inconsiderable amount of time off my life in debugging C++ issues at both compile-time and runtime (inscrutable behaviours, memory safety, broken / unstandardized tooling, platform-specific nonsense, template metaprogramming sins that I've had to debug and - worse - add to, and much more)
4. Hmm. Template specialisation and more constant-time evaluation, I think. There's probably a few others, but that's what comes to mind.
5. Uh. Pretty much everything?
- Consistent tooling that works across all platforms (I will be happy if I never have to look at a line of CMake ever again)
- Built-in dependency management
- A robust engineering culture (your code should account for failure!)
- Consistency of code itself, made possible through rustfmt and clippy
- Generics that surface issues at point of definition and not at point of instantiation
- ADTs
- Reduced reliance on human strictness / "getting it right" to, well, get it right. This isn't just the borrow checker - most APIs in Rust are designed to make using them wrong difficult. Even things like `Mutex<T>`, which combines a mutex and value, to make sure you can't access the value without locking, and you can't lock without knowing what you're locking.
- Easy multithreading through invariants that the compiler tracks (the Send+Sync traits); they're not perfect, but there's nothing like the first time you change an `iter` to Rayon's `par_iter` and your code is magically eight times faster.
- A value-based typed rich error handling scheme, so that you can see what errors a given piece of code might produce and be able to handle and propagate them with little fuss. (Similar to checked exceptions, but much more convenient! Adding some context to an error is a function call and `?` away, not a per-statement try-catch-rethrow).
---
Honestly, I could keep going for a while, but my general point is that Rust has had the ability to learn from its predecessors, and it shows. Many of the mistakes, bad ideas, or poorly-fitting features of past languages just aren't present in Rust because the language was deliberately designed to avoid them.
It's not a perfect language by any means - async Rust is the first and most obvious pain point - but it solves my problems in a much more ergonomic - and dare I say it, more fun - way than C++ ever did.
C++ is already a very complex beast as it is.
ADTs (and pattern matching) make it concise to express many different programming things.
But I'd imagine that adding it to C++ would be horrendous.
That beings aid, I don't disagree with your general point. C++ is already incredibly complex and impossible to understand as a single individual; I don't think that can be fixed without removing things from the language.
My gut feeling is that the future of C++ is not C++, it's cpp2 or Carbon.
[0]: https://en.cppreference.com/w/cpp/utility/variant
12-15 years of C++, 8 years of Rust. I generally used C++ because it was the only tool that could do the job at the time, not because I actually liked it. At best, I had a love/hate relationship with C++.
> 2. For new work projects, do you choose C++ or Rust? Why?
Almost always Rust. The main reasons:
- Cargo is awesome, and the crate ecosystem is incredible.
- I barely trust myself to write rigorously correct C++, despite having plenty of C++ experience and being extremely paranoid about code correctness. And I defintely don't trust most other people to do it correctly. (I have maintained more than my fair share of other people's C++ code, and the average C++ code is buggy garbage.) C++ is like fishing around in a drawer of extremely sharp knives in a dark kitchen; you're going to get cut.
- I trust the average Python or TypeScript developer either to write correct Rust, or to be defeated by the borrow checker. This isn't just the borrow checker—it's also bounds checking on arrays and slices, the choice to panic instead of corrupting memory, the rareness of undefined behavior (and "nasal demons", and irresponsibly aggressive optimizations) in safe Rust, the safe mulithreading support, etc.
However, I would consider using C++ in domains where correctness and security weren't important, and where I wanted to use mature frameworks. Games would be an obvious case!
> 3. For new hobby projects, do you choose C++ or Rust? Why?
Rust. Way more fun, personally. :-) I tend to like using "functional" architectures, so I don't fight the borrow checker much.
> 4. Is there something about C++ that you wish Rust had?
Well, now that const generics are in, I'm pretty happy with Rust's feature set. But for quite a while that was the best C++ feature Rust was missing.
2. I'm retired now, but I would have considered Rust for any greenfield project that didn't have a compelling reason to pick C++ (e.g. mandatory interop with an existing big C++ library).
3. Rust nearly every time. I sometimes use a layer of C++ as an FFI shim, such as when writing a gRPC client/server (the Rust libs there aren't mature).
4. The library ecosystem. Rust's crates.io seems full, but if you scratch the surface it's just a bunch of half-baked hobby projects at v0.x versions. And Cargo (the most popular build system for Rust) has essentially no support for multi-library packages, so instead of something like GLib (a single dep with lots of functionality) it's common to see even trivial programs depending on dozens or hundreds of half-baked hobby projects at v0.x versions.
5. Most of the Haskell/ML-ish functionality, such as traits and proper enums. I'm currently in the middle of a Rust project that's gotten much bigger than I expected (parsers are hard! compilers are hard! zero-copy is hard!), but it's still possible to make progress by leaning heavily on the type system.
I often describe Rust as "a dialect of C++ where the compiler forces Google Standard C++ on the world", or alternatively "the language you'd get if you hired a team of Haskell developers to write firmware".
If CLion/RustRover support was there, I'd probably switch my personal project over to Bazel+vendored deps.
Rust is not nearly so "brainy." It's a much more pragmatic and conservative language.
And it's targeted in an entirely different domain -- systems programming. Haskell is a garbage collected pure functional programming language. Rust is not anything like that.
You can see the Haskell influence in Rust today, for example the popular benchmarking library <https://crates.io/crates/criterion> is directly inspired by <https://hackage.haskell.org/package/criterion>. I've seen a lot of the same names in the Git logs and chat rooms.
In retrospect, starting with a low-level systems language and adding safety turned out to be a better idea than using `ST a` to implement borrow-checking in a high-level GC'd hosted language. I can only say "it seemed like a good idea at the time...".
There was an era of stability in the Haskell project during which it was possible to write code for industrial purposes. A lot of people were attracted to the idea of a memory-safe language that compiled to native binaries, and there wasn't much competition in that area at the time.
Then the research-oriented nature of the language and community re-asserted itself with Haskell 2010, and there was a lot of churn in both the base libraries and Hackage. Around this time was when Swift, Go, and Rust had their first releases, all of which offered memory safety and varying levels of ML/Haskell inspiration. So Haskell was suddenly less appealing, and there were other options -- why file another perf-regression GHC ticket into the void when Go has an HTTP server built right into the stdlib?
2. I would definitely choose Rust. For better or for worse, the teams I work with tend to consider developer velocity as their highest metric. Between cargo, clippy, crates and the type system, I'm orders of magnitude more productive in Rust.
3. Same thing. My life is too short to spend it debugging memory or concurrency errors.
4. There are a number of C++ libraries that have no equivalent or good bindings in Rust yet. But if you're talking of the language, no. There are C++ features that do not have an equivalent in Rust but I don't miss them.
5. From the top of my head, affine types (e.g. after a `std::move`, the type system ensures that you can't use the value anymore), proper enums, proper pattern-matching, better concurrency operations in the standard library (which pretty much requires affine types), a linter as good as clippy, built-in support for writing new linters/gradual type systems, derive macros (that have access to the AST).
I do see that peoples experience with C++ varies wildly, because shops either don't understand why modern C++ exists or they don't care. Either way, predictably, their software suffers and they will routinely run into segfaults. C++ obviously isn't perfect. It's an old language, and while it's trying to reinvent itself it will take some time.
If you ask me though, I don't give a rats ass about the safety things. My $dayjob is a big infrastructure product written in C, which practically never crashes because of memory problems. It just doesn't happen because of extreme discipline. Asserts when entering. Asserts when leaving. Magic values in structs. Sanitizers, clang-tidy, coverage, custom test frameworks, fuzzing. We have it all. And it's C! I understand newbies do better with Rust than C++. It makes a lot of sense. Still, I think that people use Rust more because it's easy to add dependencies, because it has 1 (one) build system, and because of that it's easy to build larger projects because of this. CMake has made good strides recently, but it needs to become the build system for C and C++, and we need global repositories to match Rust. Will that ever happen?
In C++, you can do whatever. Rust forces you do to some things. No discipline required by C++, absolutely required by Rust.
In C++, you can do whatever. Rust forces you to do some things. This means you must have discipline in C++, or you will do bad things. But in Rust, you can do whatever you want, the compiler has your back.
I should have said that no discipline is required in C++ to obtain a binary.
Maybe you are at a higher level than them though.
I don't know C++, but being freed from making effort to avoid a class of bugs was easily enough for me to learn Rust instead (not that this was the only reason).
So far I have used Rust very little, but what I have seen I have liked. I especially liked the much simpler inline assembly in Rust.
That's the reason for which Mozilla (and now some of the developments at Google, Facebook, Microsoft, Amazon) has started moving to Rust a while ago.
Also, I've written it somewhere else, but I'm going to repeat it:
> which practically never crashes because of memory problems
When you have memory issues, crashes are the lucky case. The common case is random memory corruption.
1. Add two signed 64-bit integers without overflowing on either side (negative or positive) and without using 128-bit integers. If the sum is going to overflow, generate an error. If the sum is not going to overflow, evaluate the sum.
2. Multiply two signed 64-bit integers without overflowing on either side (negative or positive) and without using 128-bit integers. If the product is going to overflow, generate an error. If the product is not going to overflow, evaluate the product.
Essentially under no circumstance evaluate something that might overflow. Detect the overflow and generate error. Evaluate only if the detection algorithm says there is not going to be overflow.
We can't use 128-bit integers ecause some hardware may not have a 128-bit registers. It's probably possible to solve these but it gets complex pretty soon once you begin handling all the failure modes.
Does Rust make this kind of problems easy to be solved?
https://doc.rust-lang.org/std/primitive.i64.html#method.chec...
https://doc.rust-lang.org/std/primitive.i64.html#method.chec...
I use them all the time.
In this case, it’s:
Both expressions return an Option type, which is effectively a tagged union with type safety on top so you can’t misuse it. You can either call “unwrap” to get the value out and trigger stack unwinding on error, or pattern match on the Option and its inner value in a more functional style.> 2. Multiply two signed 64 bit ints without overflow
(https://doc.rust-lang.org/std/primitive.i64.html#method.chec..., https://doc.rust-lang.org/std/primitive.i64.html#method.chec...)Well that's pointless.
And honestly, I'd rather work with those people than with people who came to Rust from languages like Python, Java, TypeScript, etc.
If the codebase that the eng is being brought into is established with good conventions, I could see it happening quickly though.
I agree they need a build system like cargo, though cmake kind of works for me.
with carbon and cppfront in the works,c++ will keep evolving
But yes, CMake is something I do not miss. Though I miss the alternatives even less. Though C++ with Bazel is actually quite attractive.
Cargo is... ok. It has rough edges, but mostly works. Various things frustrate me, though there's usually work arounds. Workspace support is still a bit half-assed -- I mean, take a look at this terminally open but rather critical issue: https://github.com/rust-lang/cargo/issues/3946 ; our $work source tree is littered with brittle hardcoded relative paths because of this basic missing feature.
Crates.io is ... not ok. It's a wild west full of hobby and abandoned projects with undisciplined massive dependency trees, has people camping out on names, and lacks even the most basic checks and balances (properly moderated submission process, expiry) and features (org namespaces) that Maven had back in like 2007.
Because of this, incredible discipline is required on large projects with large sets of third party deps or you will end up with multiple versions of things linked into your binary, or with abandoned projects in your dep tree, or just dep bloat generally.
And this ties back to cargo, too. It was only with last week's 1.74.0 release that we finally got the ability to do authenticated repositories? Again, something that Maven supported from day 1.
It's a bad culture that was, I think, imported from the node/npm world. If I were starting a serious corporate project from scratch in 2023, I'd seriously consider a model with checked-in, vendored, moderated dependencies that doesn't use crates.io (or a mirror) at all.
That being said, porting existing codebases from "old C++" (say < 2011) to "modern C++" can be a herculean task. I could be wrong, but my impression is that only clean slate codebases benefit from such modernity.
And I know that I've jumped ship from C++ to Rust a while ago. Right now, I don't see any compelling reason to go back.
This article is clearly not directed at me.
For the record, I love C++ and use it daily and will continue to do so. I also like Rust. It can be frustrating at times to have to change the way you think about structuring your program between the two, but that doesn’t mean that one or the other can’t be used effectively and safely.
Is this true for most people though? What do you think about this quote?
Frankly I've seen a lot of segmentation fault issues with C++ projects. Granted much of it can be detected pre-emptively with good tools and when a segfault happens, I can debug it quickly too using a good debugger.
But I've to say that they still do happen and I still see them happening enough while developing and testing that I cannot simply dismiss segfaults as something unimportant.
I think the author greatly underestimates the ease with which code ends up exposed to attackers. Sure, the author works on some internal thing. But where does the data come from? And data often comes in complex formats. Does the application deal with say, images or zip files? Lots of room for something to go wrong there. Projects that start internal can become part of something exposed to the outside. A modern, large project may have a few dozen dependencies, the quality of each is far from guaranteed.
For crashes, the issue is that in a complex enough project, troubeshooting is often far from trivial. Things often explode in a different place from where the bug was. My hobby project simply doesn't run under Valgrid, it's too slow for the code to run under it.
But the idea that stack overflows are not an issue if you try hard enough and pay attention is not very persuasive. It doesn't take much imagination to see that this principle will falter more and more the larger a project gets and the longer it runs. This remains THE big selling point of Rust to my mind.
c++ uses shared libraries, smaller in size for the whole system, and, you can upgrade a library separately too.
on embedded systems where storage space is restricted, rust is simply a no-go.
there are lots of embedded systems that run with glibc or musl shared libraries, if rust is used there, it will blow up the storage space with just a few executables due to the static stdlibs.
If you don’t have the budget for the standard library, don’t use it. If you somehow have the budget for glibc and not the rust standard library, call into glibc instead of the rust standard library.
rust stdlib will be bundled with those 20 rust executables, they blow up quickly.
mcu does not use stdlib of rust, it uses no-std, c++ has its own nonstdlib as well, on mcu they're similar.
Linux has proven for a very long time that if you have 100 programs, and they each have 100 dependencies, you'll probably see an occasional issue related to incompatibility.
It seems like it's suited to the mindset of unique systems maintained by sysadmins, with every app being a part of the system, quite possibly built locally just for your task.
The modern way seems to be disposable installs that cab be recreated automatically if some probably-not-worth-fixing thing happens, made of reusable multipurpose software made by people who have never heard of your application, and a bit of local code, which might be intended to someday become one of those mega-frameworks.
Even windows used to always have hassles with some visual C++ runtime or other.
Although the standard library of C isn't a big issue since it's not in the habit of breaking things, but I'd imagine Rust might want to make bigger changes since it's so new.
Shared libraries are also possible.
I started in my own time and even back then it was superior...
People like c++ for the language features... I dislike it because of all the defensive programming one must do....
I've written down some of this at https://yoric.github.io/post/safety-and-security/ .
Job postings with C++ should probably include an example source file..
To rework an old comment of mine: [0]
There is no subset of C++ which is both safe and practical for real use. The MISRA C++ subset isn't enough to provide solid assurance of the absence of undefined behaviour, for instance. For that, you need a full-bore formal verification system, akin to that of the SPARK Ada language.
This is in sharp contrast to Rust, where there really is a subset which is safe 'by construction' (called Safe Rust).
[0] https://news.ycombinator.com/item?id=27605144
That is like saying that most screws aren't load bearing, so it's fine if they fail 30% of the time. The way engineering works is that people build on reusable pieces. For example, several bugs in libjpeg have caused serious vulnerabilities in many applications, including web browsers. You cannot predict how software will be used; it will end up somewhere in someone's stack and suddenly its bugs are CVEs with huge impact.
No one ever said that safety was zero-cost. The article mentions bounds checks, but gives no measurements. Have they ever measured? Most data I've seen is that bounds checks cost less than 1% in real-world code. I will boldly assert that there has never been a product that couldn't be shipped because that 1% caused them to miss a performance goal. We could get into the weeds and go off on a tangent about linear algebra kernels and loop transformations being slower / more difficult with bounds checks, but that is an absolute niche case that can be solved with different techniques like loop versioning and static assertions.
Yes.
>Most data I've seen is that bounds checks cost less than 1% in real-world code
So? I don't know where you got you data, but the point of the article is that you don't have to pay for the stuff you don't need. Otherwise we could as well use Java or VBA because "most data" says that programs spend 99% of time waiting on IO. If your data shows that then go ahead and use it, other people have other data apparently.
I kind of don't get this attitude, TBH. A bad register allocation decision could cost 5% in the wrong circumstance, yet no one is suggesting reintroducing the register keyword, and rarely do they drop down to asm to get it. But as soon as the cost is for "safety" they're hell-bent on getting rid of it. Wth.
5% of what?
>yet no one is suggesting reintroducing the register keyword
If I understood you, you mean that some variable might be allocated on stack instead of register and we need "register" keyword to alleviate this? Not really, when such thing happens we write asm() block which has all capabilities of the old register keyword and much more. And if 5% is latency then who in real time would not get to asm() to get it? This whole talk of "1% this, 5% that" seems just some kind of reddit meme. Would you give me 1% of your income for nothing? If not then why would you increase latency by 1%? Would you write five line of assembly to get 5% raise? If you would, then why would not you want to do it to drop latency by that much?
Dropbox famously found bounds checks to be expensive in one part of their Rust code, so they introduced the equivalent of a feature flag to toggle them.
Related, the prominent Rust community member "Shnatsel" wrote an article [1] about exactly that in January. It's a great look at the problem, and gives some actual data to evaluate with.
[1]: https://shnatsel.medium.com/how-to-avoid-bounds-checks-in-ru...
E.g. the simplest case:
Becomes: This approach will have exactly the same behavior as the original program and run without bounds checks in either case. It will even throw at the right time. The cost is having two copies of the loop and a check up-front. This specific case could be done with one version, and other cases can be done with a different partitioning of the loops.I feel people are lacking imagination on what an awesome buffet of compiler transforms are available when it is both absolutely required to do bounds checks and absolutely necessary to try to eliminate them everywhere. In practice, many many loops are trivially in bounds, many other loops are cold enough or big enough that the cost of a bounds check isn't much. You don't need to unroll and version every loop in the program. And like I said, for the loops outside of this set, where iterations are not affine or the indexes are irregular, you either live with bounds checks or you go deeper on allowing static assertions to prove accesses are in bounds. That's exactly the cases where you probably want checks anyway.
However, this author, like many, does not understand what safety means.
1. There is much more to safety than memory-safety. Safety is about protecting your invariants. A program that is not safe is a program in which things that happen that are contrary to the intent of the developer.
2. Memory-safety is just one of the many tools in the safety toolbox. Without memory-safety, it's really easy to shoot yourself in the foot. With memory-safety, it's still possible, but you have to work harder at it.
3. Segmentation faults or stack overflows are what happens if you break memory-safety and you are extremely lucky. In most cases, what you get is random memory corruption. Do you really want memory corruption in a trading platform?
4. Security and safety are related, but they are different topics. See https://yoric.github.io/post/safety-and-security/ (self-promotion).
I'd much rather have a language that just "does the right thing" and or at least prevents me from doing the wrong thing. Not just leaves it as an exercise to the user to pray and hope they get it right.
I haven't had the opportunity to use rust yet but one thing I think I look forward to is a more standard way to deal with dependencies and standard built tools. languages that have a popular package manager have their issues with people using too many dependencies and possibly easier supply chain attacks but dang it's night to just get shit done and not have to futs with makefiles, CMake, Ninja, Scons, auto-config, XCode projects, Visual Studio projects, etc...
But given their high frequency trading industry, I secretly hope a competitor abuse his code base with a 2^30 priced bid or something and his company go bust.
And yet, C++ remains an undesirable coding platform, for the reason alone that there is no package manager. It's difficult to take this blog seriously when there is not even a faint mention of Cargo.
But even beyond that, I have other gripes with other fundamental points of the author's case:
> 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.
Making this claim without any numbers to back it up is a very bold move. In fact, looking at benchmarks you can see that Rust can be faster in a fair number of cases: https://benchmarksgame-team.pages.debian.net/benchmarksgame/...
Sorry, but it strikes me that the people arguing against Rust are C++ die-hards are married so strongly to status-quo that they couldn't possibly imagine a better future. Which is of course no different than die-hards in other languages.
Other than that, even as C++ aficionado, I agree there is a general resistance against safety in general and Rust in particular.
To the point that while there are compiled managed languages that can also take over some C++ roles, those die hards only see Rust as a threat.
> Are we talking about safety against hackers? The large majority of C++ applications are non-public facing.
Total nonsense. Even a PHP application will be running behind various webservers written in C/C++, the runtime is written in C, and there are plugins/libraries like libcurl also written in C/C++.
-- (Hopefully) former C++ developer of ~20 years (now with Rust!)
> 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.
Which purpose? The idea is that the majority of code that people write is in safe-land, and the bits of unsafe you use are up to you to verify that you've used it correctly. Using `unsafe` doesn't "defeat the purpose" of Rust, it tells Rust to trust that you've done something correctly.
> Well, yes, if you are going to list the “safety” of Rust compared to C++ as a pro...
"Pro" =/= "never makes mistakes". Memory safety is hard. Mistakes happen. C++ will happily crash at runtime while Rust will catch roughly 100% of mistakes in safe code at compile-time. Writing unsafe code yourself is certainly hard, but chances are that the Rust community has already done the hard part for you and made a crate featuring the data structure you're looking for. At that point, there's very few memory-related foot-guns primed for you to shoot yourself with.
> 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.
"If you ignore the thing that lets the Rust code do the thing that you want it to, then the Rust code doesn't do the specific thing you want it to."
> C... was created with the use case of it being a low level language... C++ inherit that, which makes it arguably the easiest language to code memory-intensive algorithms and data structures like hash maps, btrees, vectors and lists.
nit: "C++ inherit_s_ that". Also, this just speaks as someone who hasn't been a junior in a very long time. The number of ways you can subtly fuck up memory access in C++ is astounding, so much so that people have spent hundreds of thousands of man-hours designing memory safe languages like... Rust...
> This means that a good developer who knows how to take advantage of cache/locality, will have a good time implementing such algorithms and data structure with C++ and will very likely struggle with Rust for the same task.
Define a "good developer" more specifically. A good Rust developer certainly knows how to handle unsafe code without shaking in their boots.
> Rust mandates the use of its own compiler - rustc - which is a top driver of the LLVM stack, pretty much as clang is for C++. C++ on the other hand, has dozens of good quality compilers available in many platforms.
C++ is a 38 year old language designed in a time when you'd be happy that you had a compiler at all, let alone one that worked properly. Having multiple compilers for a language isn't a necessarily good trait, it just means that more work was spent duplicating functionality. And despite that previous statement, Rust doesn't "mandate" you use any particular compiler; there are multiple projects actively targeting other backends like GCC and cranelift.
> Although the scenario is changing rapidly, the pool of engineers with C++ background is much larger than the pool of Rust developers
See "C++ is a 38 year old language"...
> 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 you... sure you're using C++ then? This statement comes across as both a humble-brag and also just patently false if you've done anything even remotely lower-level. Do...
> Also, the quantity of teaching material and resources available to learn the language is currently overwhelmingly more abundant on the C++ side.
Quality over quantity. I'm a predominantly C and C++ developer, and the generic resources for C and especially C++ are horrible for the better part. This changes though when you look at what actual experts are putting out, either by themselves or by presenting at some convention, etc..., but these resources take knowing where to look, and many of them are useless in the process of learning the essentials, which everybody will screw up, because the language doesn't guide you, unlike Rust, which nannies you a lot (thankfully!!! you understand if you've written enough C++.)
> So if I want to bet safe in the development of a new system, I have to go with C++.
despite what I said above, C++ is prone to getting you in very unique scenarios if you're actually doing a new system (my anecdote), so you can't rely on people who aren't already skillful to even figure out that there's issues, even less how to address them. There's no guidance, really (unless you use C++Insights and hack stuff to compile-time.)
> 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.
You espouse a pop understanding of software issues. These issues are not just magic, they happen for very exact reasons, this is why we can work against them happening in the first place.
I end my comment with saying that I'm not even a big fan of Rust, it's very specifically designed, I do not like the principles that Rust builds on, but I still disagree with your analysis.
https://github.com/thepowersgang/mrustc