[HN Gopher] C++ to Rust Phrasebook
___________________________________________________________________
C++ to Rust Phrasebook
Author : wcrichton
Score : 187 points
Date : 2025-05-30 22:18 UTC (1 days ago)
(HTM) web link (cel.cs.brown.edu)
(TXT) w3m dump (cel.cs.brown.edu)
| leoh wrote:
| Epic. This is really good.
| EliRivers wrote:
| One of the common pitfalls I've seen in my time is someone
| writing a language they are familiar with in a language that just
| doesn't fit; trying to apply idioms that flow well with one
| language to another language where that's just not a good way to
| achieve the same ends.
|
| 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?
| acje wrote:
| A resource like this is a good place to discuss where the two
| languages are near and far. Of course there are going to be
| styles within each language that differ as much as the
| languages themselves.
| atq2119 wrote:
| To be fair, there's a reason for the pattern with init methods
| you're describing.
|
| 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.
| legobmw99 wrote:
| This is fine if the method you're talking about is static --
| as you point out, it's really all Rust has -- but is
| absolutely a design mistake if it is not, which I think is
| what the poster above is referring to. It's a common anti
| pattern and means you have an object that is at-best useless
| and at-worst completely broken after you call the constructor
| but before you call some member function on it
| TuxSH wrote:
| Two-phase initialization also has the added benefit of
| usually making the object have a constexpr constructor
| (usually a default constructor) and therefore making it
| eligible for constinit.
|
| That said, construct_at also exists.
| Conscat wrote:
| Nothing prevents std::vector from having a `constexpr`
| default-constructor except that it's not considered useful
| to do if you cannot follow that up with initializing its
| data in a constant context. For instance, this isn't very
| useful: 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.
|
| https://godbolt.org/z/avY4M9oMK
| EliRivers wrote:
| _To be fair, there 's a reason for the pattern with init
| methods you're describing._
|
| 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.
| Mond_ wrote:
| How would you deal with fallible construction of objects
| while avoiding exceptions in idiomatic C++?
| metaltyphoon wrote:
| This was one of the best decisions that Rust and Go did;
| not have constructors. In C# this is super annoying too,
| specially when you need an async operation to construct a
| type. This is usually done by having an private
| constructor and then using a static public method to
| create the type.
| Conscat wrote:
| Rust and Go have no form of a conversion operator (even
| if not a constructor), which makes scripting a type
| system essentially impossible. Numeric libraries in both
| of those languages are extremely cumbersome, largely for
| this reason.
| pjmlp wrote:
| Idiomatic C++ uses exceptions.
|
| 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.
| sunshowers wrote:
| Can you think of good reasons why an organization would
| hesitate to use C++ exceptions?
| atq2119 wrote:
| [delayed]
| jandrewrogers wrote:
| The standard idiom is to have a sentinel state for the
| object indicating it is invalid. For objects without
| trivial destructors or which may be read after being
| moved-from (a valid behavior in some systems code
| contexts) then you need a sentinel state anyway because
| moves in C++ are non-destructive.
|
| C++ uses deferred destruction as a standard tool to solve
| a variety of problems.
| sunshowers wrote:
| So the existence of an object of the type does not act as
| a static proof that the state is valid?
| steveklabnik wrote:
| That's correct, and "valid but unspecified state" is
| possible/common too.
| jandrewrogers wrote:
| This is correct (and I am using "invalid" here in a
| semantic sense, it is still structurally valid). There
| are a number contexts in low-level systems code where a
| static proof is not possible even in theory, so there
| needs to be a way for code to inspect object validity at
| runtime. Process address space isn't entirely private,
| external actors that your process doesn't entirely
| control can modify it e.g. via DMA.
|
| 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.
| sunshowers wrote:
| Definitely agree that there's plenty of cases in systems
| code where static proofs are impossible. That makes it
| all the worse when you give up on static proofs in places
| where they are possible.
| jjmarr wrote:
| > which may be read after being moved-from (a valid
| behaviour in some systems code contexts)
|
| 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++.
|
| [1] https://en.cppreference.com/w/cpp/utility/move.html
| jandrewrogers wrote:
| I am using "invalid" here in the semantic sense of not
| containing a meaningful value. It is not invalid in a
| structural sense.
| atq2119 wrote:
| [delayed]
| Conscat wrote:
| > the object construction can be done with static methods
|
| 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>
| fooker wrote:
| Right, when construction is fallible you need a factory.
|
| 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.
| pjmlp wrote:
| I hate with passion two phase initialisation, C++ libraries
| that are bare bones C libraries wrapped in an _extern "C" { }_,
| malloc()/free(), C style coding and such.
| pornel wrote:
| The worst pitfall is Rust references == pointers.
|
| 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.
| trealira wrote:
| >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
|
| 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.
| hardwaregeek wrote:
| The rationale is probably that it's better for C++ devs to
| write non idiomatic Rust than to keep writing unsafe C++. Like
| unless they use unsafe and completely circumvent the borrow
| checker, it's still gonna be safer. Not letting perfect be the
| enemy of good and all.
|
| 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.
| npalli wrote:
| Inverting the original intent, this is great, learn how Rust
| improves certain things and use that to write better C++ if
| possible.
| tialaramex wrote:
| Plenty of C++ practitioners who still intend to mostly write
| C++ after learning some Rust said it actually gave them useful
| insights they'll be applying in future C++
|
| 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.
| 90s_dev wrote:
| I will be using this to learn both Rust and C++ and see which one
| I like better.
| Tyr42 wrote:
| I've taken a look, and it's definitely expecting you to know
| some C++. Or at least, it spends equal time on both, which
| means it can't warn you about the foot guns in c++.
| skrishnamurthi wrote:
| It's definitely written from the perspective of someone who
| "knows C++". But I put that in quotes because there are (at
| least) two interpretations of that phrase. There's the person
| who doesn't know any C++ at all, and for that person, this is
| useless. But there's the person who knows a baseline of C++
| but doesn't know modern C++, and they _can_ use this as a way
| to modernize their C++ code while ignoring the Rust bits. But
| either way, yes, it doesn 't warn you about C++ footguns
| (other than pointing you towards Rust <-;).
| jpc0 wrote:
| There are patterns diffused in this paper that are modern
| C++ only in the sense that anything post C++11 is modern
| C++. That was 14 years ago, you will be hard pressed to
| find a toolchain that doesn't support C++17 at this point,
| yes there is probably some unfortunate person building for
| debian old-stable or some ancient but still supported
| redhat but at that point you know you aren't following
| modern practices and you have your reasons.
| pjmlp wrote:
| I will also debate that plenty of "modern" C++ features
| that people attribute to C++11, were already possible
| throughout C++ARM to C++03, but apparently many either
| weren't paying attention, or only renaming their C files
| into .cpp/.cxx/.C.
|
| Just like the low level stuff done by MFC, and how much
| more ergonomic CSet++, OWL and VCL happened to be.
| Mond_ wrote:
| This isn't much of a tutorial or learning guide, it's a lookup
| table that roughly pattern matches C++ to Rust.
|
| Probably not the best place to start learning.
| jpc0 wrote:
| There are so many different flavours of C++ put there that this
| guide doesn't exactly do itself the credit it deserves.
|
| 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.
| Toritori12 wrote:
| C++ modules still not supported on VSCode, always pissed when I
| get notifications from this 5 y.o. thread [0].
|
| 0: https://github.com/microsoft/vscode-cpptools/issues/6302
| IshKebab wrote:
| Does Clangd support it? It's much better than Microsoft's C++
| extension - and open source!
| jpc0 wrote:
| Yes this.
|
| 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.
| pjmlp wrote:
| Visual Studio and Clion are the ones currently with the best
| experience regarding modules.
|
| And in VS could be much better, but EDG has other priorities.
|
| VSCode isn't really that great option for C/C++.
| m-schuetz wrote:
| I prefer vscode simply because VS is excruciatingly slow.
| e.g. the file open pane in vscode pretty much instantly
| lists the file I'm looking for, while the counterpart in VS
| (ctrl+,) takes several seconds and intermixes search
| results for files and file contents, when I'm only
| interested in files.
| NotCamelCase wrote:
| > VS (ctrl+,) takes several seconds and intermixes search
| results for files and file contents, when I'm only
| interested in files.
|
| 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.
| Toritori12 wrote:
| Same here, those "mini" delays are not worth.
| pjmlp wrote:
| Ctrl+, followed by _f filename_ , you will get the file.
|
| 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.
| m-schuetz wrote:
| I know it gets you the file...after several seconds.
| Vscode gives it instantly. I use this feature so often,
| it's basically a dealbreaker for VS.
|
| I keep both editors open, but VS is basically just there
| to hit the compile button and for the occasional
| debugging.
| pjmlp wrote:
| Dunno, time to check your plugins slowing down the IDE.
|
| Better not having Resharper around.
| m-schuetz wrote:
| I have zero plugins installed. VS is just so slow that
| it's even outmatched by a javascript/Electron app like
| vscode.
| tom_ wrote:
| DPack (free) has a useable file browser. I use Visual
| Assist (commercial) these days and that has one too. Both
| pop up pretty much instantly.
|
| Both available from the extension marketplace.
| pjmlp wrote:
| As someone that has been around C and C++ communities since
| 1990's, I also expect that in the long term Rust won't be able
| to escape this phenomenon, even with editions.
|
| 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,...
| leoh wrote:
| Hasn't been the case the last 10y. If anything there has been
| convergence.
| eslaught wrote:
| Async vs. non-async is the main example today. There are
| libraries that support one or the other, or sometimes one
| library will have two usage modes (effectively two
| different code bases) because you can't really mix them.
|
| In the future who knows, because we don't know what
| features will get added to the language.
| m-schuetz wrote:
| Are C++ modules actually production ready now? Last I checked,
| they still weren't properly supported accross all major
| compilers.
| pjmlp wrote:
| Depends on which requirements one has.
|
| 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.
| psyclobe wrote:
| No...
| amelius wrote:
| I noticed that for most of the examples the Rust version is more
| verbose.
| pornel wrote:
| C++ syntax and semantics are optimized for C++ idioms, and
| Rust's aren't.
|
| 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++.
| amelius wrote:
| So you are saying that this approach teaches C++ users to use
| the wrong idioms in Rust?
| pornel wrote:
| _Wrong_ is too strong. The code is okay given the
| constraint -- this is a guide for C++ programmers thinking
| in C++ terms, not for teaching purely idiomatic Rust from
| the ground up.
| amelius wrote:
| Ok, do you have any examples to convince me?
| mrlongroots wrote:
| Which, as a cpp programmer trying to pick up Rust, is
| honestly fine to begin with. Once you've written varying
| amounts of code in 5-10 programming languages, it is
| incredibly tedious to flip through pages trying to teach
| you how if conditions work and how for loops work: my
| brain doesn't pay attention even if I try.
|
| 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".
| modulus1 wrote:
| And they even made the C++ version more verbose than it should
| have been. Most people would write:
|
| class Person { int age = 0; };
|
| I wish rust would make default struct field values this easy to
| write.
| steveklabnik wrote:
| It's coming soonish! There's an accepted RFC that's getting
| close to being ready to stabilize https://github.com/rust-
| lang/rust/issues/132162
| Animats wrote:
| The discussion of traits vs. classes glosses over a major
| difference - Rust traits have no associated data, and you cannot
| access the data of a parent trait. Trying to do object-oriented
| programming in Rust quickly leads to a mess. This is a huge
| problem if you try to write C++ in Rust.
|
| 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.
___________________________________________________________________
(page generated 2025-05-31 23:01 UTC)