I've seen this idea from a few people and I don't get it at all.
Rust is certainly not the simplest language you'll run into, but C++ is incredibly baroque, they're not really comparable on this axis.
One difference which is already important and I think will grow only more important over time is that Rust's Editions give it permission to go back and fix things, so it does - where in C++ it's like venturing into a hoarder's home when you trip over things which are abandoned in favour of a newer shinier alternative.
C++'s complexity is coming from how eager it is to let you shoot yourself in the foot. Rust will make you sweat blood to prove the bird in the sky you're shooting at is really not your own foot.
Yes, I think it's pretty easy to be sure that this isn't where the complexity comes from when there's literally a method to do that on arrays. I'm not even sure which language you're talking about here, because you can call `.len()` in Rust or `.size()` in C++.
If you're trying to remember what the language is where there's no immediately obvious straightforward way to get the length of an array, it's not Rust or C++; you must have been thinking of C.
This is a case of exactly the "hoarder" modality in C++ that I described. When somebody says array in C++ you think of std::array, the newer shiny C++ class which you're asked to use instead of the native array types. The native array types in C++ still exist and indeed do not provide methods like size()
In Rust the arrays aren't stunted left over primitive types which weren't gifted modern features, array.len() works because all Rust's types get features like this, not just the latest and greatest stuff.
While it is true that only class types have member functions in C++, that does not mean that objects of other types demand the use of macros. C++ also supports non-member functions, and the standard library contains a fair amount of these; including `std::size` that can be used to "get" the length of an array.
(C++ arrays are different from arrays in many other programming languages, though not necessarily Rust, in that their type specifies their length, so in a way this is something you already "have" but certainly there are cases where it is convenient to "get" this information from an object.)
Because of the type system, with its ML influence, two macro systems, the stuff on nightly that many folks enjoy using, having to rely on external crates for proper error handling and async/await features.
Additionally, given its ML influence, too many people enjoy doing Haskell level FP programming in Rust, which puts off those not yet skilled in the FP arts.
Also the borrow checker is the Rust version of Haskell burrito blogs with monads, it is hard to get how to design with it in mind, and when one gets it, it isn't that easy to explain to others still trying to figure it out.
Hence why from the outside people get this opinion over Rust.
Naturally those of us with experience in compilers, type systems theory and such, see it differently, we are at another level of understanding.
> Also the borrow checker is the Rust version of Haskell burrito blogs with monads, it is hard to get how to design with it in mind,
Eh. Haskell monads are a math-centric way of performing operations on wrapped types (stuff inside a monad). Rust borrow checker is way more pragmatic-centric, i.e. how can we prevent certain behaviors.
The difference being you don't see Monads being replaced by Tree-Monads, without impacting the code.
> and when one gets it, it isn't that easy to explain to others still trying to figure it out.
So is going from 0-based to X-based arrays (where X is an integer); Or learning a new keyboard layout. Just because it's hard (unfamiliar) doesn't mean it's impossible.
>where in C++ it's like venturing into a hoarder's home when you trip over things which are abandoned in favour of a newer shinier alternative
Perl is so bad about this that I once worked on a very old codebase in which I could tell approximately when it was written based on which features were being used.
Because they’re both complicated languages, but for different reasons. Rust didn’t really solve memory safety, it just pushed all the complexity into the type system. If one was struggling with memory errors in C++ that’s nice. If one was using Java, that still sucks.
Furthermore some programmers really like complicated languages like Rust, Haskell, etc others like straightforward languages like Go, Python, etc.
Python is absolutely not straighforward, it's a huge language with many moving parts and gotchas.
Although, I admit, both are very easy to start programming in and to start shipping broken projects that appear working at first. They are good for learning programming, but terrible for production.
> Rust didn’t really solve memory safety, it just pushed all the complexity into the type system.
Yes, that's what it did, and that's the right tradeoff in most cases. Where the compiler can't make a reasonable default choice, it should force the programmer to make a choice, and then sanity-check it.
> If one was struggling with memory errors in C++ that’s nice. If one was using Java, that still sucks.
It's nice for those struggling with uncaught exceptions, null pointer bugs, mutithreading bugs, and confusing stateful object graphs in Java.
Many of us strongly prefer handling some complexity ourselves that we can then tackle with tests, CI, fuzzing, etc if it means we don’t have to jump through hoops to satisfy the compiler before we can even see our code running.
Yes, Golang and Python and Java are very easy to start programming in. And unless we’re dealing with some really complex problem, like next-gen cryptocurrencies ;), by the time the Rust teams have gotten their code working and nice and proper as any Rust code needs to be, the Golang/Python/Java teams have already released to customers.
If one wants to be super-cautios and move accordingly slower to be really really sure they have no memory errors then that’s fine. There’s a market for that stuff. But selling this approach as a general solution is disingenuous.
> the Golang/Python/Java teams have already released to customers.
...have already released a broken prototype that appears to be working for now.
I'm yet to see a case where manually "hardening" your software is faster than writing a "similarly-good" program in Rust. That's just anti-automation. Why repeat the same work in every project and bloat your codebase, when the compiler can carry that burden for you? In my experience, Rust makes you write production-grade software faster than when using other languages.
> But selling this approach as a general solution is disingenuous.
I agree! There are legitimate cases where releasing a broken prototype as quickly as possible is important. There's room for that.
But I agrue that it's not the case for most "serious" production software that would be maintained for any period of time. And that Rust is the preferable option for writing such production software.
The idea that one needs Rust to write reliable software is not only ridiculous at a logical level, it is also contradicted by the fact that Rust is but a tiny, irrelevant subset of safety-critical software or really all software.
If it were really “preferable for writing such production software”, more people would be using it.
But they don’t because the compiler does not carry the burden for you. It puts the burden of writing code in a way that satisfies the Rust lifetime management design on you, and that’s what you’ll be doing forever.
There zero proof that Rust software is higher quality than equivalent e.g. Swift or Java. The memory-safety trick only works against C and C++.
> The idea that one needs Rust to write reliable software is not only ridiculous at a logical level
I never said that. I said that it's more appropriate and productive.
> If it were really “preferable for writing such production software”, more people would be using it.
There are many valid reasons for not using it, even in new projects. Such as human preferences, lack of specific libraries, and simply not having time to learn yet another language for a non-10x benefit.
> the compiler does not carry the burden for you. It puts the burden of writing code in a way that satisfies the Rust lifetime management design on you, and that’s what you’ll be doing forever.
It carries a different burden for me. In exchange for the burden that you mention, I don't worry about mutable aliasing bugs (including data races), breaking the code by refactoring it, manually unit-testing basic properties that could be types, debugging non-obvious non-local effects (such as exceptions and null values invisibly propagating through the layers of the app), micro-optimizing the performance manually, and so on. The benefits are known. I've almost forgot how to use a debugger at this point.
It's a tradeoff. I find it more comfortable and less cognitively-taxing to work this way, once the initial learning curve is behind. Less unpredictable problems to distract you from solving the problem. The borrow checker problems largely go away as you get more comfortable with it. And it's a skill that's instantly transferrable between all Rust projects, unlike project-specific defensive practices in other languages (the "manual hardening" needed to get decent quality). It's a long-term investment that automates a small, but not insignificant part of your work.
> There zero proof that Rust software is higher quality than equivalent e.g. Swift or Java.
Yeah, the thing is, the moment your software has crossed the boundary of success, it becomes a burden, not a blessing. You are now stuck with it and you better have made sure that you're happy with it the way you built it, because you're not moving on from this project any time soon.
Maybe Rust isn't optimized for throwaway projects and that's fine.
I think you can do throwaway projects and prototypes about as fast in rust.
But it requires a different mindset which I think many in our industry finds hard to work with. Rust exposes the imperfections rather than hiding them until you explicitly check for them like in most other languages.
Just clone and unwrap liberally and throwaway projects go fast.
What we’re seeing in practice is not throwaway software but successful companies built over many years using e.g. Golang. When they grow big enough to pay the Rust tax and can’t squeeze more performance out of their set-up they switch to Rust.
Starting something in Rust only makes sense for very few domains and software categories.
I wonder how Go would fare if it had a production-ready LLVM/GCC backend. I wouldn't be surprised if much of the performance differences between Go and C/C++/Rust comes down to optimized codegen (rather than GC, which is what people often complain about Go). Not saying that GC pauses might not be an issue in some cases, but still...
As someone who has to work with a janky mix of C++ and Python. I would prefer getting rid of both altogether. I personally am not a fan of Python or the Python ecosystem whatsoever. It's certainly not speeding things up with those two.
Go pushes the complexity into the programmer, that is how one ends with source code like Kubernetes, when the language doesn't provide all the tooling.
Python belongs to the complicated languages section, people that think Python is straightforward never bothered reading all the manuals, nor had to put up with all the breaking changes throughout its history, it wasn't only 2 => 3, that was the major event, every release breaks something, even if little.
> Go pushes the complexity into the programmer, that is how one ends with source code like Kubernetes, when the language doesn't provide all the tooling.
I am not sure what you mean by this. I write go code pretty much everyday and that code looks vaguely the same as it would do in C#, or Python, or JavaScript.
> Python belongs to the complicated languages section, people that think Python is straightforward never bothered reading all the manuals, nor had to put up with all the breaking changes throughout its history, it wasn't only 2 => 3, that was the major event, every release breaks something, even if little.
I've read the manuals. Most docs appear to be reasonably well written, even if I find many of the examples a bit odd (I've never been a big fan of Monty Python, so the spam and eggs examples are all a bit odd).
The 2 => 3 change was probably a big thing for those migrating all the existing libraries, frameworks. But as someone that uses it for prototyping stuff, I barely noticed the difference.
C++ has editions too btw. C++11, C++14, C++17, etc. These are opt in and allowed to break compatibility, although that is very rarely done in practice.
That's one difference. And the other important differences are:
- Rust apps can depend on library "headers" written in other editions. That's the whole deal with editions! Breaking changes are local to your own code and don't fracture the ecosystem.
- Rust has a built-in tool that automatically migrates your code to the next edition while preserving its behavior. In C++, upgrading to the next standard is left as an exercise for the reader (just like everything else). And that's why it's done so rarely and so slowly.
Not really, because the crates they depend on cannot expose APIs with semantic changes across versions.
Also it requires everything to be compiled with the same compiler, from source code.
There are tools available in some C++ compilers for migration like clang, note the difference between ISO languages with multiple implementations, and one driven by its reference compiler.
> Not really, because the crates they depend on cannot expose APIs with semantic changes across versions.
Not sure what you're talking about. Any specific examples?
> Also it requires everything to be compiled with the same compiler, from source code.
It's not related to editions at all. It's related to not having an implicit stable ABI.
It's possible have a dynamic Rust library that exposes a repr(C) interface, compile it into an .so using one version of the compiler, and then compile the dependent "pure Rust" crates using another compiler that's just going to read the metadata ("headers") of that library and link the final binary together. Same as in C and C++. You just can't compile any Rust code into a stable dynamic library, by defalut. (You can still always compile into a dylib that needs a specific compiler version)
As the other commenter responded there, your example isn't about editions at all. It's about mixing ABIs and mixing multiple versions of the language runtime. Those are entirely separate issues.
You're correct that the possible changes in editions are very limited. But editions don't hinder interoperability in any way. They are designed not to. Today, there are no interoperability problems caused by editions specifically.
> compromises will be required, specially regarding possible incompatible semantic differences across editions.
That's just an assumption in your head. 4 editions later, it still hasn't manifested in any way.
4 editions later, Rust is yet to be used at the scale of C and C++ across the industry, my point on the comment is how editions will look like after 50 years of Rust history.
ABIs and multiple versions of the language runtime, are part of what defines a language ecosystem, hence why editions don't really cover as much as people think they do.
Editions will look like band-aids that don't fully solve the cruft accumulated over the 50 years. That's not hard to predict. It's still a very useful mechanism that slows down the accumulation of said cruft. I'm yet to see a language that has a better stability/evolution story
In any case, while I as language geek have these kind of discussions, given current progress in AI systems, my point of view is that we will get the next evolution in programming systems, thus it won't matter much if it is C, C++, Rust, C#, Java, Go or whatever.
We (as in the industry) will eventually get reliable ways to generate applications directly to machine code, just as optimizing compilers took a couple of decades to beat hand written Assembly, and generate reliable optimized code.
So editions, regardless of what they offer, might not be as relevant in such a timeframe from a couple of decades ahead.
The machine code will always be generated from "something". A one-line informal prompt isn't enough. There will always be people who write specs. Even the current languages are already far from "machine code" and could be considered "specs", albeit low-level
As the response to that comment points out, you are confusing editions for ABI changes. Different editions are purely a source-level change in the language. All editions are ABI-compatible.
Not really, because one thing that apparently I haven't gotten across is that they don't cover semantic changes, only grammar ones for the most part.
What is the compiler supposed to generate if code from edition X calls code in edition X + 10, with a lambda written in X + 5, expecting using a specific language construct that has changed semantics across editions, maybe even more than once?
> one thing that apparently I haven't gotten across is that they don't cover semantic changes, only grammar ones for the most part.
You have gotten it across just fine.
We're trying to get across that no one is ever going to do "global" incompatible semantic changes in editions. That's been understood from the start. Exactly because of the problems that you describe.
It would work as intended? I’m not sure what problem you are trying to point out. The lambda would compile in the edition in which it is written, resulting in a code unit passed to the other crate(s). As mentioned, the ABI is stable across editions. To put in your words, there are no semantic changes to the ABI across editions.
C++ shipping new and slightly incompatible versions of the entire language every three years isn't Editions, there was a proposal to attempt Editions (under the name "Epochs") for C++ but it faced significant headwinds and was abandoned.
Rust is certainly not the simplest language you'll run into, but C++ is incredibly baroque, they're not really comparable on this axis.
One difference which is already important and I think will grow only more important over time is that Rust's Editions give it permission to go back and fix things, so it does - where in C++ it's like venturing into a hoarder's home when you trip over things which are abandoned in favour of a newer shinier alternative.