Comparison to Go?
Comparison to Go?
Posted Jan 5, 2025 17:48 UTC (Sun) by NYKevin (subscriber, #129325)In reply to: Comparison to Go? by tialaramex
Parent article: Preventing data races with Pony
> It's true that I've read a _lot_ of C++ initially because I wanted to understand HashMap, and thus hashbrown, and thus the Swiss Tables and so I ended up watching CppCon and reading the source code. So I probably could write pretty good C++ today if I wanted to (I do not want to). But that all comes after I learned Rust. I read the C++ memory ordering model after I used the Rust implementation of exactly the same model. When I first saw std::expected and std::optional I already knew Result and Option very well. When I read the paper proposing the C++ 11 move semantic I had already been using Rust's default move assignment semantic and thinking this is obvious for some time. When I read Barry Revzin's "trivial union" work I came at that being intimately familiar with the details of MaybeUninit<T> which is basically what Barry is trying to be able to do in C++.
The average professional C++ developer understands, at best, half of those things. To be more specific:
* Most C++ developers probably know that std::hash_map is O(1) in all important operations, and they probably have a loose understanding of how a hash table works in principle. They probably know what "open" and "closed" addressing mean. They probably don't know the details of a modern hash table implementation.
* Most C++ developers haven't the faintest idea what a "memory ordering model" is or how to reason about it. They probably understand that races exist and can be prevented with locking. They probably know that atomics exist, but not how to use them correctly.
* Most C++ developers have a general understanding of move semantics, but if you ask them to explain how std::forward works internally, they will not have a damn clue ("I dunno, just copy the sample code from cppreference and it'll work"). They can tell you not to write "return std::move(foo);", but they cannot tell you why (some developers know enough to vaguely gesture at NVRO, which they may wrongly refer to as "copy elision," but this is an incomplete explanation at best).
But the same also goes for Rust:
* Most Rust developers think of HashMap as a black box, much like their C++ counterparts.
* Rust developers have probably heard of Send and Sync, and are probably aware that mutable objects which will be shared between threads need to be protected by some type in std::sync, usually a lock or atomic of some kind. In practice, the idiot-proof options are Mutex and RwLock, so most developers are probably going to reach for one of those (possibly wrapped in an Arc if the borrow checker complains about it). The smart developers know that OnceLock/LazyLock exist. Of course, there are situations where you need to reach for atomics, but the expectation is that you know what you're doing before you go down that road.
* Rust's move semantics are simpler than C++'s (no moved-from objects, no rvalue references, no move/copy overload resolution, moves are memcpy, etc.), so I would expect Rust developers to have a firmer understanding of them in practice, but that also means that Rust's semantics are easier to learn, especially if you already know about C++'s semantics. Going from C++ to Rust is mostly an exercise in "stop thinking so damn hard and just write it the obvious way."
* Rust does introduce lifetime semantics, which make things harder, but only if you want to have long-lived borrows. If you internalize the rule that borrows should be ephemeral, most borrow checker errors can be boiled down to "the borrow checker thinks this borrow is not ephemeral enough." The remainder can be phrased as "you really do need this borrow to be non-ephemeral, so now you have to explain how that works to the borrow checker (or just wrap it in Rc/Arc and call it a day)." C++ developers should already be familiar with the general concept that references should not outlive the underlying object (or else your code will be littered with UAF bugs).
Posted Jan 5, 2025 18:30 UTC (Sun)
by khim (subscriber, #9252)
[Link] (1 responses)
I think that's the central piece: some people try to understand how things really work – and some don't think it's even worth doing. The former build some kind of mental worldmap in their head. Even if sometimes they build wrong map that doesn't correspond to the reality – it's easy to fix it, when such difference is found. And these mental maps are very similar in C++ and Rust. Rust simplifies many things (while simultaneously making some other more complicated… TANSTAAFL at it's best) and replaces STL with more Lisp/ML/Haskel style standard library (but if you build a mental map of how things work the actual names of the functions are not that important… there's Google, after all) – and thus Rust is perceived as “C++ done right”. But for people of “just copy the sample code from cppreference and it'll work” parlance, who collect useful snippets of code without even trying to understand how and why code looks this and not that way… for them switch from C or C++ to Rust is really painful, because all their collections of copy-pasted code are, suddenly, worthless (or almost worthless). But these people also have trouble embracing new paradigms that are arriving with new versions of C++! Why “wrongly”? It's the official name for what is happening. It may not be very appropriate because what is eliminated is not copy, but materialization of prvalue, but trying to invent your own terms doesn't help if you want to discuss things with someone, better to use terms that official documentation is using… even if they are not 100% correct.
Posted Jan 5, 2025 19:31 UTC (Sun)
by NYKevin (subscriber, #129325)
[Link]
You're right, I mixed NVRO up with so-called "guaranteed copy elision," which is not actually a form of copy elision at all, but falls out of the semantics of prvalues. Which just goes to show how hard it is to keep track of C++ semantics even when you're trying to understand it all properly.
> if you ask them to explain how std::forward works internally, they will not have a damn clue ("I dunno, just copy the sample code from cppreference and it'll work")
Comparison to Go?
Comparison to Go?