An example I've seen a lot is a C thinker writing C++ classes with an init() function; sure, it works, but the C++ way is to do that in constructors. (All those about to start listing exceptions to that C++ idiom, please save it to the end, thanks!) The C thinker is still thinking about objects as "allocate memory, then set values" rather than the C++ way where allocation and initialisation are wrapped together into a single action (from the programmer's point of view).
So what are these pitfalls for a C++ thinker when writing Rust? This "phrasebook" is embracing the idea of taking a C++ way of thinking and applying it to Rust, which I'm sure will be fine for many situations, but what are the C++ phrases that are just the wrong way to do things in Rust?
C++ constructors can't return values. If construction is fallible, the only way to communicate the error is via C++ exceptions. If you're in a code base that embraces exceptions, that's fine. But (C++) exceptions kind of suck, so many code bases don't, and then you have to find some alternatives.
Over the years, I've increasingly adopted a pattern where the constructor is private in this case and the object construction can be done with static methods - which is a bit more like Rust, actually.
That said, construct_at also exists.
constinit vector<int> v;
But this would be more so: constinit vector<int> v(16, 1); // Fill with 16 1's.
And the reason we can't do this wouldn't be solved by splitting it into multiple functions.EDIT: Actually, come to think of it, C++20's vector already supports the first example. It's just not used much that way because it's not very helpful.
Without prejudice on any other reasons, the most common reason for this pattern I've seen is people thinking in languages that basically don't have constructors, yet writing C++. It's not a good reason.
The standard doesn't allow to disable language features.
Anyone that goes into the dark side of disabling language features is writing unidiomatic C++ with compiler specific extensions.
C++ uses deferred destruction as a standard tool to solve a variety of problems.
The C++ compiler largely assumes that such static proof is possible by default and has no way of knowing if it is not. To address this, the C++ language has added features for annotating objects to indicate that static proofs of state are not possible at compile-time (e.g. std::launder).
Database kernels are the most extreme example of this because most objects in the address space don’t own their memory address and the mechanism that temporarily puts an object at a particular memory address is not visible at compile-time. Consequently, object location and state has to be resolved dynamically at runtime.
std::move as applied to standard library types will leave the object in a "valid but unspecified state".[1] If you're leaving the object in an invalid state (one where the invariants are broken), you're not writing idiomatic C++.
For objects that don't have that property, you're just exchanging one kind of badness in the design for a different but ultimately equivalent badness.
I've done that a lot too, but I found that free functions are much better for this than static member functions, because you can't get CTAD from static member functions. For example, with constructors we could write:
vector{1, 2, 3}; // deduces vector<int>
And with a static member, we would need: vector<int>::init(1, 2, 3);
With a free function, we could write: make_vector(1, 2, 3); // returns vector<int>
Constructors are called in surprising places when running a C++ program. For example, think of a copy constructor failing somewhere far away from the code you are writing. If C++ allowed construction to fail, the control flow of propagating these errors would be tedious and invasive.
Hence exceptions as the only way to fail in a construction.
They are implemented as pointers, but their role is to give temporary (often exclusive) access that is restricted to a statically know scope, which is pretty specific and fits only some uses of some pointers/C++ references. In C++ pointers typically mean avoiding copying, but Rust references avoid storing/keeping the data. When these goals don't overlap, people get stuck with a dreadful "does not live long enough" whack-a-mole.
You could have a vector of references to heap allocated data, as long as the references were parametrized by the same lifetime. You might do this if implementing a tree iterator using a vector as a stack, for instance. That goes beyond a statically known scope. But implementing a mutable iterator the same way would require a stack of mutable pointers (and therefore unsafe code whenever you dereference them), since mutable references have to be unique. That does seem like a bad limitation.
Plus idiomatic rust isn’t that strict a definition. Clippy will guide you for most of the simple stuff and the rest isn’t always worth following. Like people who try to do stuff “correctly” with traits often end up with way more complexity than it’s worth.
We can also see in the committee proposal papers "Rust does X" has for years now been a good comeback when you need to show that X is a realistic choice not just for languages like Python which may be less concerned about performance and incur a heavy runtime, a garbage collector, etc., but also a "real" language like C++. The paper which landed code.contains("FOO") in the C++ string handling code is an example, there's a long list of languages which do this but they made sure to mention Rust.
Just like the low level stuff done by MFC, and how much more ergonomic CSet++, OWL and VCL happened to be.
Probably not the best place to start learning.
There are easy ways to implement stuff like enums with members in C++, just put an anonymous enum inside a class/struct, its possible but marked as not possible.
Likewise when discussing modules in rust while completely ignoring the existence of modules in C++ that is actually support by modern tooling.
There are other places where there are similar issues I have with the text (you can just add a compiler flag and get the same behaviour. Don’t even try and argue “but rust just does it out of the box”, I would need to rewrite my entire codebase vs just adding a few bits of text to my build system, these things are not equivalent)
They didn’t discuss much about FFI at all, generally sayings “theres a crate for that and if there isn’t let us know”, in my experience the crates are not amazing for esoteric things (anything graphics’s related, ffmpeg is another one) and are actually significantly more painful to use that just writing in a restricted version of C++.
Rust has a happy path, and they are broadening it incrementally, but theres is a lifetime of history before rust even existed in C++ that isn’t that easy to sweep under the rug.
Clangd-19 which is current stable has very good support for modules. The only issues I’ve encountered is with import std; which quite honestly is bleeding edge.
Your tooling (clangd+cmake) has to be pretty modern t those two are also the easiest to just upgrade since it’s dev time only. And obviously if it’s a discussion you have a C++20 compatible compiler. I’m happily using modules with gcc-14, clangd 19 and cmake 3.28 other than clangd 19 those are just packages you can install in Ubuntu 24.04 which is over a year old at this point.
And in VS could be much better, but EDG has other priorities.
VSCode isn't really that great option for C/C++.
I hate this a lot. It's gotten so bad the last couple of releases to the point that I try and use VS Code more in lieue of VS except debugging.
How they could let such fundamental functionality get broken is beyond me.
The only reasons I use VSCode are the plugins I cannot get on VS, like Powershell, Rust, Azure tooling, and for stuff like Next.js, better use an editor that is anyway a browser in disguise.
Performance has never been a part of my decision flowchart.
I keep both editors open, but VS is basically just there to hit the compile button and for the occasional debugging.
Better not having Resharper around.
Both available from the extension marketplace.
Like any other programming language that has made it into the top 10 over several decades of production code.
The happy path will get fuzzier, as more humans have their own opinion on what means to write code on the ecosystem, with various kinds of backgrounds and their own agendas, companies whose IT refuses to upgrade, the amount of implementations in the industry increases, teachers not bothering to keep up to date,...
In the future who knows, because we don't know what features will get added to the language.
If you develop that for servers or mobile/desktop applications it might look more homegenous, but their are a lot of segments beyond those out there.
If staying only on VC++ or clang latest, with MSBuild or CMake/ninja, they kind of are, on my hobby coding I have been using modules for quite a while now, check the C++ projects on Github.
Tip, for node native modules, which node-gyp probably will never support them, they are supported via cmake.js.
This guide does exactly that in C++ for methods on enums.
For members, won't this result in all instances of an "enum" having all fields? That's not really comparable to Rust enums, if that's what you mean.
Rust is more related to ML-family languages than C-family and OOP, so it needs to "emulate" some C++ idioms.
This goes both ways, e.g. equivalent of Rust's pattern matching on enums with data translates to verbose and clunky C++.
This is more like: how to survive rustc as a cpp programmer which is honestly your mindframe when you start out, and it sets you up for "okay now that you speak the syntax, this is how to really think in rust terms".
class Person { int age = 0; };
I wish rust would make default struct field values this easy to write.
There's no mention of ownership at all.
> Many libraries in Rust will offer two versions of an API, one which returns a Result or Option type and one of which panics, so that the interpretation of the error (expected exceptional case or programmer bug) can be chosen by the caller.
Huh? Usually, in Rust you just put .unwrap() or .expect() on a function call that returns a result if you want a panic.
More generally, most of the differences between Rust and C++ relate not on how to write imperative code, but how to lay out connections between data structures. Most hard problems in Rust relate to ownership design. That's true in C++, too, but you don't discover them until the program crashes at run time.
leoh•1d ago