I think it’s accurate to say that Rust’s syntax and semantics are both simpler than those of modern C++. That isn’t to say that Rust is a simple language (it manifestly isn’t), only that being simpler than C++ is not a hard bar to clear.
(The proxy I use for this is the ratio of years spent programming to insane acronyms known. C++ has given me SFINAE, CTAD, SIOF, etc. so far, while Rust has only given me RPIT.)
Don’t forget RAII and CRTP (Curiously Recurring Template Pattern).
As someone who has extensive experience in C++, Haskell, and Rust, I can confidently say that none of them is simple, it’s just that C++ has a mountain of legacy / technical debt that Rust has to deal with. If C++ were to be invented today, it would look a lot more like Rust.
> If C++ were to be invented today, it would look a lot more like Rust.
I'm not sure about this, because the world without C++ would have been quite different from today. The main complication comes from the fact that C++ was considered a much higher-level language than today, so its absence would have made other alternatives evolve differently.
To be fair RPIT is part of a family, but they're actually just a weird way to name a variety of features that it made sense to give similar syntax in Rust. "IT" in RPIT means "impl Trait" the syntax, but what that syntax does varies by position (the P).
Return Position Impl Trait is Existential Types, but Argument Position Impl Trait is just a simpler syntax for some Generics, same syntax, different meaning. This is actually one of the places where Rust is probably easier to pick up in use than to understand in theory. APIT and RPIT both "feel" right, but in type theory these are radically different features.
In that way it's like some natural languages, native English speakers don't notice a bunch of the grammar rules they learned as kids, those rules just "feel" right and not formally taught.
“Simpler” is so fuzzy that it doesn’t accurately express what’s going on. Rust doesn’t have the backwards compat baggage that C++ has, so it’s simpler.
However, I can today easily write clean C++ that’s easy to understand, and using containers, algos and smart pointers is also robust. This is what the C++ community has settled on, while pushing complexity into libraries.
The Rust community has settled on async, moves and references (with the lovely lifetime annotations) and other cognitive overhead, because they think that getting errors at compile time offsets all the mental overhead of those constructs. Then they gaslight all the beginners which complain about that mental overhead.
So in theory Rust may be simpler, but idiomatic Rust isn’t. It’s differently complex.
C++ has exactly the same async mechanism as Rust. Moves and references are much simpler in Rust - values only ever move and copies require an explicit clone unless the type is copyable in which case the compiler will implicitly clone if there’s conflicting ownership in the code.
This is orders of magnitude simpler than c++ overloading of copy/move assignment and constructors, even when you follow the rule of 0 (not least of which is that you don’t to teach people about it or how to keep code exception safe).
It’s kind of amusing to claim that c++ is simpler than Rust when it’s demonstrably not true - a new person to Rust is more productive after 6 months than a person new to c++. That implies the bar to getting proficient is low. A person can reach intermediate more quickly implying the bar to the next levels is lower. And it’s easier to maintain a rust project due to cargo meaning a usable easy build system out of the box that the entire community uses vs opinionated choices each project has to do, and macros that are easier to manage than preprocesser mess. So easier to maintain and easier to jump into any project. Oh and tests and mictobenchmarks out of the box and a very rich ecosystem of supporting libraries.
Idiomatic rust is simpler than idiomatic c++ and it’s clearer what constitutes idiomatic rust whereas c++ in practice is rarely idiomatic and there are going to be conflicting ideas on what is idiomatic.
This is ignoring the very real benefits of rarely to never dealing with a whole class of difficult to debug bugs (threading and memory safety) in the vast majority of code you’ll write .
Only in Rust is the async use idiomatic, the C++ co_* is pretty new and not yet ready for prime time.
References are not at all simpler, they're memory-safe, which in conjunction with lifetimes makes them significantly more complex to understand and write. This is a known problem...
In C++ I copy by default and move if I want to optimise. Moves also happen behind the scenes as an optimisation, this is how things should be, don't push the work on the programmer!
I don't agree with your claim that a new person to Rust is more productive. What's that based on? In my experience, a new person to Rust will write memory-safe code by default, which is good, but they pay the cost of Rust's lack of ergonomics forever. See https://news.ycombinator.com/item?id=40172033 which covers async, refactoring and design issues that plague even experienced programmers.
> Idiomatic rust is simpler than idiomatic c++ and it’s clearer what constitutes idiomatic rust whereas c++ in practice is rarely idiomatic and there are going to be conflicting ideas on what is idiomatic.
It's really not, because C++ copies by default which is typically both memory-safe and very easy. If using smart pointers and clone was idiomatic in Rust, you'd have a point, instead references and lifetimes are idiomatic.
Modern C++ has a pretty clear definition, albeit it's evolving as new things are added to the standard. C++ is moving too fast recently, but then again so is Rust.
> This is ignoring the very real benefits of rarely to never dealing with a whole class of difficult to debug bugs (threading and memory safety) in the vast majority of code you’ll write .
That, cargo and lack of historical baggage are the only real benefits of Rust compared to C++. The lack of ergonomics, slow compile times and to some extent the aggressive community make it far from a clear choice.
> C++ has exactly the same async mechanism as Rust
Is this true? I ask because I've seen it mentioned that a C++ co_await suspends that call frame alone (thus the call returns to the caller and resumes immediately), where as Rust's .await suspends the entire call stack (all frames up to and including the caller). If true, in what sense are these mechanisms same?
Not the mechanism itself but the same in the context of this statement:
> The Rust community has settled on async, moves and references (with the lovely lifetime annotations) and other cognitive overhead
The rust community has settled on async in the same way that the C++ has - it's an optional keyword that is some syntactic sugar over compiler-generated stuff. The main unique "cognitive overhead" is that Rust forces you to write down lifetimes when they can't be inferred, but the rest is the same or worse in C++.
As for the async mechanism, I don't believe there's magic in Rust that suspends the entire call stack. It walks up the stack the same as in C++. If you don't await an async function call or one returning a future, then there's no suspension and the inner function is executed until the first suspension point if I recall correctly same as in C++. There's nuances in differences but at a high level they're very similar in that it's generating a state machine and nothing more; Rust does require a runtime whereas C++ can do without since the Rust picked generic futures that work on all runtimes whereas in C++ you have to have the runtime provide the future implementation (or have the future not be part of any runtime).
I have written and worked with a sizable C++ code base multiple times in my past career and I can't really agree.
> Rust doesn’t have the backwards compat baggage that C++ has, so it’s simpler.
C++'s "compat baggage" is mostly self-imposed, because it didn't have any working isolation system (including module systems) for a very long time, and the current C++ module system is still not fully isolating.
> [T]hey think that getting errors at compile time offsets all the mental overhead of those constructs.
This complaint might be true for some (but not all) people, but even in that case Rust will give a full memory safety (with some specific but reasonable definition). You can always use other languages if you don't find the memory safety that important---after all, there are many other axes of safety to consider and balance. But if you do need the memory safety for many good reasons, Rust is definitely a better language than C++ to achieve that.
Also C++ might be forgiving, but it takes much time to learn C++ to avoid many pitfalls and yet I regularly got tripped up by things that I do know but didn't have in mind at the time. I hate the "modern C++" argument; such argument is only reasonable when you are forbidden to write any legacy code without explicitly overloading. (Again, C++ has no real isolation to allow such mode switching.)
(The proxy I use for this is the ratio of years spent programming to insane acronyms known. C++ has given me SFINAE, CTAD, SIOF, etc. so far, while Rust has only given me RPIT.)