It's really crazy how much C++ has already evolved since the start of my career (with C++98). C++ has been the millennium falcon of languages; creaky and old, not pretty to look at, but still good where it counts.
That said, I completely switched over to rust many months ago and rarely look back. Rust is a tribute to C++, an incorporation of lessons learned that sheds all of the baggage C++ will never be rid of. Rust also has less of learning curve than C++ (although both are quite high), simply because C++ has so many odds and ends to keep track of these days.
Rust has also made me realize how bad class based OO is as an abstraction. OO glues together operator overloading, methods, inheritance, all into one package. Rust breaks those concepts into component parts that provide much better abstractions with less keywords and jargon to worry about.
I too started my career with C++ (and spent 10 years using it almost exclusively, before moving to JS some time back).
The first code block in the article made my jaw drop, and my first thought was "what the hell has happened to C++? I was only away for 5 years". Then I saw the "simple style" example and breathed a sigh of relief. That is the C/C++ I recognize. Even the third example with structs is not necessary. The author is right. Sometimes reusability is overrated. Duplication is better than the wrong abstraction.
Yea, but the struct example delivers on the idea of reusability (where the pythagorean triples algorithm is a stand in for some complicated algorithm) in any situation where this might apply.
It's a useful answer to the point: Well what if it was 15 for loops with a bunch of esoteric function calls. The struct example would still be better.
> It's really crazy how much C++ has already evolved
To borrow an old bit of snark: I don't know what the "default" programming language of 2025 will look like, but I know it will be called C++.
Like Ethernet (the original subject of that snark), C++ has evolved into a form unrecognizable to those who saw its birth. Unlike Ethernet, the older forms haven't gone away. They still reappear often in large/old/important codebases, keeping compiler writers busy and everyone else less productive. I'm generally the last person to suggest replacing things that already work, but C++ really does just need to get paved over.
OP has been proselytizing Rust on HN like it's the new coming of the Messiah.
This kind of language advertising where one can't even bother mentioning one negative thing about the product is sad but effective, because people want to believe that there's a perfect language out there...
Correcting the same misleading statements over and over again is a chat bot's job and not an enjoyable task for a human being. I was just pointing out that you're probably wasting your time, but you're of course free to discuss the finer points of class-based OO or Rust's learning curve for the Nth time, if you're so inclined.
C++ has issues and problems because it's too old and it's not going to get rid of the old baggage, hence it will have to carry it forward while adding new stuff on top (concepts, ranges...). Those issues are not going away, they will only get worse over time.
Rust has different kinds of issues and problems because it's still relatively young. For instance, pretty bad compile times (even compared to C++ with heavy templates use). But that's solvable, it will just take time. Most issues with the language itself that I've personally encountered were of the kind "it's not stabilized yet" or "it's not fully implemented yet".
I cut my teeth writing very C-ish C++98 for an embedded system in the '90s and the C++20 ranges example code from the blog might as well have been in an entirely different language. Sadly, I may have to stop listing C++ as a language I'm familiar with on my resume.
I write very C-ish C++17, that is, C + constexpr + a very restricted subset of templates (you can only use integral types as arguments). No template metaprogramming, ever. By extension you're also not allowed to use _Generic macro of the new C standard, if you absolutely absolutely must use it, then use C++ template. Metaprogramming with constexpr, static if etc is fair game. No class methods, just use C style POD with functions. You cannot use references (just pointers). You're not allowed to use any C++ library, but any C library is fair game.
This works really well in practice for us. We use GCC to compile.
After a foray into C++-ish C++ I also write fairly C-ish C++.
I see it as being like walking into an armoury and being able to choose anything you want. Chances are, if you want to resolve a domestic dispute, you don't want a claymore. Or a Howitzer. Or an aircraft carrier, or a lump of unshielded semi-critical plutonium. But that's OK, you don't have to choose them. You can walk right past those shelves to the shelf with the megaphone and the padded vest, and just take those.
> I see it as being like walking into an armoury and being able to choose anything you want. Chances are, if you want to resolve a domestic dispute, you don't want a claymore. Or a Howitzer. Or an aircraft carrier, or a lump of unshielded semi-critical plutonium. But that's OK, you don't have to choose them. You can walk right past those shelves to the shelf with the megaphone and the padded vest, and just take those.
Maybe my analogy would be clearer if I mentioned that half of the munitions in the stockpile were irrevocably pointed at my foot... but that which half is which is not clearly labeled and depends on subtleties of architecture and compiler. :)
Once you allow only pointers, you know if you see foo.bar, foo is a value on the stack.
As a function parameter, you know that mutable calls with "." don't change state outside of the function.
I have nothing particularly against references, especially since they look nicer with function pointers. But, you can do everything with pointers alone and if you open the gates of hell.. erm I mean C++ then there will be countless questions about lvalue references, rvalue references whether it's ok to use T&& whether it's ok to use std::forward, std::move etc... It's just simpler to stick to pointers.
I changed my resume to list "C++98" instead of "C++" a few years ago. But I could probably just leave it off entirely as I endeavor to write as little C++ as possible... JavaScript is getting the same way.
I'm not sure why anyone today would start a greenfield project in C++ instead of D, rust or Nim. Maybe in niches where C++ is deeply rooted, like games.
Because C++ is a stable, reliable and well supported (read: production-ready) language that works on many platforms.
Last I heard D was definitely not all of those (it doesn't even have a straight story on garbage collection, as its creator said himself! [1]), and I'm not sure about Rust or Nim.
The visual tooling is still very meh though. Just compare the kind of code completion you get for C++ (even something as gnarly as Boost) in modern IDEs, to what RLS can do in VSCode.
I wouldn't say that d "doesn't have a straight story on garbage collection." It's solidly a garbage-collected language, and although there is interest in changing it, it's not there yet. D isn't entirely stable (features regularly get deprecated and then removed over the course of 5-10 versions), but why do you say it isn't reliable?
In many ways, it's about being the first to do something. I write a lot of code around DPDK for packet processing. It's a pure C library. I know rust has good C bindings, but I heard Go did as well. I started a project in go using cgo for the dpdk parts.
What a nightmare.
I spent more time figuring out the idiosyncrasies of cgo than actually writing useful code. And in many cases, cgo couldn't actually do what I needed to do. I had to write some functions and see inside of the .go file.
I'm sure rust would have been easier to call C with since the languages bare inherently more compatible, but I don't want to go down that path again right now.
The Go language has weak, C interoperability. The parents are strong in it. You wouldnt be going down same path. More like the opposite one that is closer to your actual goals.
I recommend Rust or D for your next try since compiler quality is best for those. D will be easiest to learn. You can always use unsafe in either if memory management gets in way.
For the UI rust has bindings for a lot of stuff and it's easy to generate more. There are also more and more native options. It's also very good for that use case because it makes certain high-performance things much easier/safer. Threading is a joy for example.
I've worked on an image processing project in C for a while and am now doing one in rust and would never want to go back. The code is much cleaner and easy to maintain. The zero-cost abstractions really pay off, and the safety guarantees means you don't spend so much of your time chasing heisenbugs from subtle threading and locking issues.
For the UI rust has bindings for a lot of stuff and it's easy to generate more.
I use Rust as my main language, but as far as I have seen gtk-rs is the only somewhat mature binding for an existing UI toolkit. This is not coincidental, since Gtk+ is a C (with objects) toolkit, it is much easier to bind than toolkits in other languages. Unfortunately, outside Linux, Gtk+ also looks pretty out of place.
AFAIK, there isn't any binding for e.g. Qt or Cocoa that has wide API coverage and is mature. Definitely not for production-level work. I think currently the only viable solution is to write a core in C++, expose C functions and write the UI in C++, Swift, etc.
I searched and saw some Qt bindings as well, but didn't check if they're complete. I've decided to just do 100% rust so I use conrod instead of bindings. But the cases where I've tried the GObject stuff it did indeed seem pretty well done, at least in the Gstreamer case. I'd expect that over time even this gets fixed and Qt bindings become first class as well.
> I think currently the only viable solution is to write a core in C++, expose C functions and write the UI in C++, Swift, etc.
This is the worst case and is not that bad. It does force you to write in two languages but on the other hand it enforces a cleaner UI vs backend split and that's not a bad habit to have.
I don't know about rust, but d has bindings to gtk and qt, in addition to its own hardware-accelerated ui library (dlangui), and nim can include c headers directly and so easily use gtk. Nim, rust, and D are all capable of c-level performance (numbers, pointer munging, etc. are all available the same as in c).
Because "it's the language you know best" is a perfectly valid reason. Another valid reason is that, if used properly, C++ performance blows other languages out of the water.
Based on what I've seen, rust really can be just as fast.
D and Nim are a bit more iffy since they have built in GC. It can be turned off, but at that point you lose a major selling point over C++, memory safety.
I like Nim but its ffi, while really good, is not effortless. You have to wrap every function you call, which in large C libraries can be an incredible pain in the neck.
There are tools to help you out, like c2nim, but in my experience the output it produces requires fixing to get it working, and of course when the upstream library changes or adds to its API those sorts of things have to be taken into account as well.
Nim compiles to C, so can at least make use of fast C compilers. But the solution really seems to be to just use a fully dynamic language like Common Lisp that can compile, load, and redefine (hotswap) everything incrementally so you're not having to start from nothing all the time.
Compiling to C is not a compile performance advantage. If anything it slows you down as described in the article (header files). It's just not as bad as C++.
On big projects, linking times are really the bottleneck.
Sure, incremental linkers exist [1], but all of them tend to do O(n) work on every invocation. Source control software, even developed explicitly to scale [2], behaves the same way. Makefiles as well; Tup [3] tries to solve this, in vain since the linking step is still holding everything up. There is so much inherent inability to scale built into our tools. So on big projects everything grinds to a halt as you cannot buy enough developers and hardware to keep up with O(n^2) forever.
It is very fast for certain projects, but link times on some can make up for that. I have a toy program using vibe.d that takes around 1s to compile and 10s to link. I could probably make that faster by switching to gold, but I didn't yet.
I'm not sure how anything over 100ms could be considered "fast compile" for a toy program... Basically Pascal, or simple c compilers like tcc should be the benchmark imnho.
That's not to say I don't allow for trade-offs.. I gladly trade some milliseconds (or, grudgingly seconds) for features.
Most of the compile time of that particular program is spent parsing HTML templates and converting them to D code. This is all done by D code provided by a library that runs at compile time as part of a template function instantiation in my code. So the compile time is inflated a bit in this case.
I don't think Rust has anything comparable to implementation inheritance like C++ has? Getting implementation inheritance from pure composition is essentially a "tying the knot" exercise, where you must ensure that "virtual" method calls are always dispatched on through something like an existential type, so that they can reach the right implementation for the actual "child" object. (The right question to ask is "when do we actually need this, ffs?" Almost never, as it turns out - it's just needless complexity. Hence, "prefer composition over inheritance!")
That said, I completely switched over to rust many months ago and rarely look back. Rust is a tribute to C++, an incorporation of lessons learned that sheds all of the baggage C++ will never be rid of. Rust also has less of learning curve than C++ (although both are quite high), simply because C++ has so many odds and ends to keep track of these days.
Rust has also made me realize how bad class based OO is as an abstraction. OO glues together operator overloading, methods, inheritance, all into one package. Rust breaks those concepts into component parts that provide much better abstractions with less keywords and jargon to worry about.