[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)