It also fails to mention Apollo Aegis, coded in Apollo's extended Pascal, and both at introduction and retirement was more advanced than Unix. Some key features still are not seen in Linux or most BSDs.
And, it fails to mention SerenityOS, in C++.
It also fails to mention Apollo Aegis, coded in Apollo's extended Pascal, and both at introduction and retirement was more advanced than Unix. Some key features still are not seen in Linux or most BSDs.
And, it fails to mention SerenityOS, in C++.
C++ is long in the tooth, and if you want to talk desperation, C++ is really struggling with its identity now that Rust is on the scene. Before there were some pretty good arguments to use it, but in light of Rust, the best argument for C++ is just that it has a larger ecosystem (which says more about how old C++ is than how good it is as a language). That argument becomes less convincing with each passing day.
IMO in 2022 there’s not a good reason to start a new project in C++ unless that’s all you know, or you have some constraint tied to C++.
I personally don't agree. I can start with performance and continue with a better compiler ecosystem, and add suitability for low resource applications. I'm just not scratching the surface here.
Rust is not bad, but it's not a replacement of C++. They aspire to be different things.
The code I'm developing is running with ~1.7M iterations/core/second. Every one of these iterations contain another set of 1000 iterations or so (the number is variable, so I don't remember the exact mean, but the total is a lot). Also, this number is on an 8 year old system.
More benchmarks are here: https://benchmarksgame-team.pages.debian.net/benchmarksgame/...
For example, I'm using a lot of vector, matrix and array accesses in my code. A lot of these are copied around for computation. Moving that amount of data will trigger a lot of checks in Rust, and will create a slowdown inevitably.
This is, in my experience, the overwhelming majority of idiomatic Rust code. For cases where it can't, and performance is absolutely critical, there are easy escape hatches that let you explicitly elide the checks yourself.
In Rust, moves are always a simple memcpy from one place to another, that's it! With non `Copy` types this destroys the original value which is why the compiler makes sure you don't access it after it's moved.
There is also `Clone` which is generally a deep copy of everything, but this also doesn't involve any runtime checks as long as you aren't using reference counted pointers or something.
There are bounds checks when indexing into vectors and whatnot though, but non checked methods are available.
I am curious if there are any examples of things you wanted to do but couldn't with Rusts move semantics.
After using both C++ and Rust I personally feel that the way Rust handles moves makes a lot more sense and ends up being much more ergonomic. In C++ moves feel messy and complex due to them being added on rather than having the language built around them from the start. It's also not ideal that C++ moves must leave values hanging around in an unspecified state rather than destroying them immediately, even if there are a few cases where that is useful, most of the time it's not.
Rust also doesn't have any of the crazy `&&T` rvalue reference stuff or require explicitly defining move constructors since everything is movable and all moves are bitwise copies.
It also has quite a few pros, and many people think it's better the way it is. Since moves are memcopies, they can never fail, meaning there's no opportunities for panics, which means it's much easier to optimize. They aren't arbitrarily expensive; you always know what the cost is. As you mentioned, there's no need for a "moved-from" state, which can save space and/or program logic.
What specific operations happen slower in Rust than in C++?
Unless you can provide numbers to back this claim up, I'll continue to rely on my measurements that bounds checking (in Virgil) costs anywhere from 0.5% to 3% of performance. It's sometimes more or less in other languages, but it is by far not the reason any programming language has bad performance. I have no reason to suspect that it costs more in Rust, other than misleading microbenchmarks.
I observe (-Ofast -march=native -std=c++20 ; CPU is intel 6900k with performance governor on Arch... blasting at 4GHz) :
- clang 13: 8.93 ms per iteration
- gcc 11: 9.46 ms per iteration
so roughly around 9ms.
Replacing
return mat[r * matrix_size + c];
by return mat.at(r * matrix_size + c);
I observe- clang 13: 20.95 ms per iteration
- gcc 11: 18.11 ms per iteration
so literally more than twice as slow. I also tried with libc++ instead of libstdc++ for clang and the results did not meaningfully change (around 9ms without bound-checking and 21 ms with).
I could counter by just writing a Virgil program and turning off bounds check with a compiler flag. We could stare at the machine code together; it's literally an additional load, compare, and branch. The Virgil compiler loves eliminating bounds checks when it can. I know your original comment was in the context of the STL, but it really muddies the waters to see huge piles of code get inlined or not depending on compiler flags. Machine code is what matters.
Regardless, this is still microbenchmarking. Maybe matrix multiply is an important kernel, but we need to benchmark whole programs. If I turn off bounds checks in a big program like the Virgil compiler, I cannot measure more than about 1.5% performance difference.
I just used godbolt to quickly share the code. On my computer I tried with -Ofast -march=native (broadwell)
> I could counter by just writing a Virgil program and turning off bounds check with a compiler flag. We could stare at the machine code together; it's literally an additional load, compare, and branch.
This sounds like Virgil is not vectorizing, which makes the comparison much less useful.
I see much more than a couple instructions changed there: https://gcc.godbolt.org/z/8jPMb734x
That's just even more confounding variables. We are, after all, not even doing experiments with Rust vectors, which is what your original comment was about. You wrote examples in C++ and we went down a wormhole already, but I let it slip since at least it was empirical. But I think we shouldn't get distracted with even more side alleys now. Bounds checks are literally just a couple machine instructions, and often eliminated by the compiler anyway, which enables vectorization.
they are very close to C++ ones, down to the stack unwinding to report the panic in case of bound error if I'm not mistaken.
> That's just even more confounding variables.
No they are not. We are trying to see how much bound checks costs. If you compare between suboptimal programs the comparison is meaningless (or rather not interesting to anyone) - the starting point has to be the absolute best performance that it is possible to get, and add the worst-case bound-checking (I'm happy for you if you never have to worry about the worst case though!)
> Bounds checks are literally just a couple machine instructions, and often eliminated by the compiler anyway
please provide a source for this ? sure, if you use spans as other commenters mentioned, that moves the checking at the span creation time but that only works for the simplest cases where you are going to access linearily - and I would say that it's a library feature rather than a compiler one.
for(double v : vec) { } // or any sub-span you can take from it
in C++ also does not need bound-checks by design but this kind of construct also utterly does not matter for so HPC workloads.I can look into my pdfs folders and bring out dozens of papers where the core algorithms all use funky non-linear indexing schemes where you cannot just iterate a range (algorithms based on accessing i, i-1, i+1, or accessing i and N-i, or accessing even / odd values, etc etc) - how would you implement an FFT for instance ? This is the code that matters !
A compiler can do loop versioning for that. And they do. Hotspot C2 (and Graal, too) does a ton of loop optimizations, partitioning the index space into in-bounds and potentially out-of-bounds ranges, unrolling loops, peeling the first iteration off a loop, generating code before a loop to dispatch to a customized version if everything will be in bounds.
When a compiler is doing that sophisticated of loop transforms, you are not measuring the cost of bounds checks anymore, you are measuring a whole host of other things. And sometimes if a loop is just a little different, the results can be disastrously bad or miraculously good. Which is why microbenchmarking is so fraught with peril. A microbenchmark might be written assuming a simple model of the computation and then a sophisticated compiler with analyses never dreamed of comes along and completely reorganizes the code. And it cuts both ways; a C++ compiler might do some unrolling, fusion, tiling, vectorization, software pipelining or interchange on your loops and suddenly you aren't measuring bounds check cost anymore, but loop optimization heuristics that have been perturbed by their presence. You end up studying second-order effects and not realizing it. And it keeps running away from you the more you try to study it.
>> That's just even more confounding variables.
> No they are not. We are trying to see how much bound checks costs. If you compare between suboptimal programs the comparison is meaningless (or rather not interesting to anyone) - the starting point has to be the absolute best performance that it is possible to get, and add the worst-case bound-checking (I'm happy for you if you never have to worry about the worst
We are always comparing suboptimal programs. No compiler is producing optimal code, otherwise they would dead-code eliminate everything not explicitly intertwined into program output, statically evaluate half of our microbenchmarks, and replace them with table lookups.
You're going down a linear algebra rabbit hole trying to come up with a result that paints bounds checks in the worst possible light. If this is the real problem you have, maybe your linear algebra kernels would be better off just using pointers, or you could even try Fortran or handwritten assembly instead, if it is so important. Unsafe by default is bad IMHO. For real programs bounds checks really don't matter that much. Where this thread is going is all about loop optimizers, and they don't really get a chance to go nuts on most code, so I think we're way off in the weeds.
You can shoot yourself in the foot with Rust, it's just that you need to explicitly point a loaded gun at your foot and pull the trigger, whereas C++ feels like any excuse to shoot you in the foot is just too tempting to ignore even if you were trying pretty hard not to have that happen.
Example, on Visual Studio define _ITERATOR_DEBUG_LEVEL to 1, enable /analize as part of the build.
While not Rust like, it is already much better than not caring.
so does real-life code ? I don't know about you but I pretty much never know the size of my data at compile time.
> If it's declared as "static"
again, real-life code calls math operations defined in other libraries, sometimes even proprietary.
Whole-program optimization (LTO) can deal with that. Also, Rust inlines across modules much more aggressively than C++ does, even without LTO, so optimization will be more effective and its bounds-checking won't have as much (if any) runtime overhead. Especially if you write your Rust code idiomatically, as others have already suggested. Your earlier Rust example was practically designed to inhibit any attempt at optimization. Simply iterating over a slice of the vector, rather than the indices, results in a much tighter inner loop as it only needs to check the bounds once.
That being said, in this case I think it would be better to have fixed-size matrices (using const generic/template arguments) so that the bounds are encoded in the types and known to the compiler locally without relying on whole-program optimization.
This is pretty much word-for-word what I've seen in other similar disagreements. Rust Skeptic transcribes their solution from its original programming language into Rust line by line, producing something nobody using Rust would actually write. They find that Rust performs poorly. Rust Proponent writes it the way you'd expect someone who actually knows Rust to write it, and performance meets or exceeds the original. Sometimes they even catch a few bugs.
Yes, if you don't know Rust, and you think it's just a weird re-skin of C++ that can be gsub'd from one to another, you're going to have a bad time. If you're an expert in C++ and have never written Rust, you probably aren't going to beat your finely-tuned C++ program with a ten minute Rust translation. But someone with a year of experience in Rust is probably going to be able to rewrite it in half an hour and come within spitting distance of the thing you've optimized by hand over the course of a few years.
I've written Rust for half a decade and I'm not sure I've ever actually explicitly indexed into a Vec or slice in production code. If I needed to, and it was in a hot loop, and it wasn't a fixed-size array whose size is known at compile time... there's always `get_unchecked()` which is functionally identical to indexing into a C++ vec without `at`.
most C++ code is not "idiomatic C++" either, it's code which looks like this:
https://github.com/cdemel/OpenCV/blob/master/modules/calib3d...
or this
https://github.com/madronalabs/madronalib/blob/master/source...
or this
https://github.com/MTG/essentia/blob/master/src/3rdparty/spl...
etc ... which is in general code from science papers written in pseudocode which is ported at 2AM by tired astrophysics or DSP masters students to have something to show to their advisor. You can have whatever abstraction you want but they won't be used because the point is not to write rust or C++ or anything but to get some pseudo-MATLABish thing to run ASAP, and that won't have anything that looks like range / span, only arrays being indexed raw.
it is absolutely not the proper way and a complete anti-pattern
So long as you make sure your program is correct you never need to worry about indices being out of bounds. Requiring bounds checking is a sign you need to eliminate the errors in your software.
This makes me wonder, why are people writing software with errors in it in the first place? Even master programmers seem to be too lazy and careless to remove the errors from their software. What gives?
Here is a honest answer. The problem is frankly unsolvable thanks to the halting problem. It's impossible to determine in general whether a program is going to reach a certain state except by testing all potential inputs and even that is only going to give you an approximate answer. If it were possible we would have written a program that solves the problem through static analysis.
That is why I write all my code in Pascal, where you can enable bound checking for []
Enable it for a debug build, and you an be sure there are no overflows of any kind, and when it runs the program has pretty much no errors at all. Disable it for the release build, and it runs as fast as if it was written in C
It's a saner default option tbh.
EDIT: lol don’t write code five minuets after waking, the reply to this has far better code
(EDIT: incidentally, I was also curious about a non-extern function, but the compiler is seeing through all of my attempts to call a native Rust function with an opaque body here...)
That being said this code will also be a no-op instead of... something worse, if the vector is empty, so, they're not directly comparable, but they weren't really in the first place, as you mention, so...
for i in &v[1..num]
instead of all the iter take skip stuff?https://rust.godbolt.org/z/er9Phcr3c
Yes, you still have the bounds check when constructing the slice, but that's fine.
And since it's probably slightly more common to iterate over the length of the vector anyway:
And look, putting a limit that the compiler and guarantee once outside the critical loop eliminates it. You're already using unsafe. If you really want to shoot yourself in the foot with this nonsense you can use a get unchecked or an unchecked assumption
https://rust.godbolt.org/z/naeaYad5T
On top of that, noone would write rust code like that. This version has one bounds check at the beginning and is a lot more idiomatic
for(double v : std::span(vec).subspan(1)) { }
in C++ and that will be safe and not need a bound check except an user-provided one at the creation of the span (-1 for C++ here :-)) - and also not matter at all regarding the bound checks problem which will occur as soon as you don't just iterate linearly which is extremely common in the kind of problems that end up showing at the top of the profiler and the whole point of this conversation.I'm confused. Doesn't that apply to the C cowboy way of doing things? You introduce security vulnerability after vulnerability and then lots of people have to hire expert security consultants all the time. Your snark just makes no sense to me. Fixing an ArrayOutOfBoundsException in Java is something even a novice programmer with less than a year experience can do. No expensive consultant needed.
The Java vulnerabilities aren't even in the same class as C. They are usually quite dumb shit like class loading remote class files using obsolete features that nobody even remembers (log4shell). It's like setting up ssh with a default password (raspberry pi). It happens but it's rare because of the sheer amount of "incompetence" required that it requires lightning to strike twice.
Sometimes they're called auditors. Or "formal audits" because people think if they call something "formal" it's somehow different from not being "formal".
https://users.rust-lang.org/t/how-to-create-large-objects-di...
Make a MaybeUninit<Doodad> on the heap, initialise it, and then unsafely assume_init() remembering to write your safety justification ("I initialised this, so it's fine") and get a Doodad, on the heap.
The reason it isn't mentioned in that 2019 u.r-l.o post is that MaybeUninit wasn't stabilized until mid-2019.
Making Rust perform the same as C++ in database engines requires marking large portions of the code base as "unsafe", while requiring substantially more lines of code due to being less expressive. Note that this doesn't imply that the code is actually unsafe, just that the Rust compiler cannot reason about the safety of correct and idiomatic code in database engines.
And this is why state-of-the-art database engines are still written in C++. There is not much point in using Rust if doing so requires disabling the safety features and writing a lot more code to achieve the same result.
The state of the art database engines are written in C++ (and Java) because at the time they were developed Rust didn't exist.
The downside of kernel bypass is that the kernel is no longer transparently hiding the relationship between storage, memory, and related low-level hardware mechanics. A consequence of this is that the mutability and lifetime of most objects cannot be evaluated at compile-time, which Rust relies on for its safety model. The hardware can hold mutable references to memory that are not represented in software and which don't understand your object model. How do you manage lifetimes when references to objects are not visible in the code? When an object does not have a fixed address? And so on. This is not trivial even in C++ but the language does allow you to write transparent abstractions that hide this behind something that behaves like a reference without complaining.
There are proven models that guarantee safety under these constraints, widely used in database engines. Instead of ownership semantics, they are based on dynamic scheduling semantics. The execution scheduler can dynamically detect unsafe (or other) situations and rewrite the execution schedule to guarantee safety and forward progress without violating other invariants. This is why, as a simple example, some databases never seem to produce deadlocks -- deadlock situations still occur but they are dynamically detected before they occur and are resolved transparently by editing the lock and execution graph.
Some major classes of database architecture optimization don't work in garbage collected languages. For this reason, you never see state-of-the-art database engines written in e.g. Java.
For example, the Bevy game engine's entity system works like a small in-memory database, and also replaces ownership (games also have lots of mutability and lifetimes that aren't clear at compile time) with careful dynamic scheduling. Users of this system just request a particular kind of access (mutable or immutable) to some set of entities, and the engine schedules them to avoid conflicts.
I'm not that familiar with databases so maybe I'm just totally missing what you're talking about, but from what it sounds like this is not something that would require large portions of a Rust program to be unsafe.
Overall I think the idea that Rust relies on knowing specific mutability and lifetimes at compile time is a bit of a misunderstanding of how the borrow checker works. It's rather a sort of glue that describes relationships between APIs, and when you are coming up with a custom approach to ensure safety it is still a useful language you can speak on the boundaries.
Almost every object in a database engine, whether transient or persistent, lives outside the virtual memory model provided by the OS. Because it is in user space, this is part of the database code and no longer transparent to the compiler. To a compiler, these objects have no fixed address, have an ambiguous number of mutable references at any point in time, and may have no address at all (e.g. if moved to storage). This is safe, and you can write abstractions in C++ that make this transparent to the developer, but the compiler can still see it and Rust does not like what it sees.
Performance is not the only reason to do this. The OS implementation is tightly coupled to the silicon implementation of virtual memory. Very large storage volumes can exceed the limits of what the hardware can support, but user space software implementations have few practical limits on scale.
Where did you get that from? There is nothing more in Rust than there is in C++ that relies on virtual memory subsystem. Rust is perfectly able to compile for platforms with no OS at all.
> This is safe, and you can write abstractions in C++ that make this transparent to the developer, but the compiler can still see it and Rust does not like what it sees.
Rust can do the same abstractions as C++ can. This is pretty much a standard for low-level crates, which come with some unsafe code, wrapped in higher-level, easy and safe to use abstractions.
As a consequence of implementing some of these kernel functions in user space, it is not possible to determine object lifetimes or track mutable references at compile-time. Any object created in memory that bypasses the kernel mechanisms is unsafe as far as Rust is concerned. In a database, the vast majority of your objects are constructed on this type of memory.
You can implement this in Rust, but essentially the entire database kernel will be "unsafe". At which point, why bother with Rust? C++ is significantly more expressive at writing this type of code, and it is not trivial even in C++.
To be fair, we're trying to do a bit of a different thing (in-memory incrementally maintained views on streaming data) than most existing databases, so you could argue that it is not an apples-to-apples comparison.
But there are plenty of other databases written in non-C++ languages -- there is even TiDB which is written in Rust too.
It's really disappointing, especially when the language has incorporated so many other excellent performance improvements like Google's SwissTable and pattern-defeating quicksort.
https://github.com/rust-lang/rust/issues/29594
https://matklad.github.io/2020/10/03/fast-thread-locals-in-r...
The company I work at uses a pinned nightly rust in order to access some soon-to-be-stabilized features that simplify our life a bit. We update our pinned nightly in lockstep with stable rust releases. In practice, we've almost never had problems with using nightly rust - those are still very well tested, and problems are caught early and fast. The Rust test suite is quite thorough!
However, we do try to avoid using features that don't have a clear trajectory towards stabilization, so for us to consider thread_locals, we'd need to have some very solid proof that it would fix some critical performance problems.
I suspect every org will have their own policy, and while the majority will use stable rust, it's not like nightly rust is completely unthinkable.
[0]: https://blog.rust-lang.org/2020/12/16/rust-survey-2020.html
On top of that, the C++/WinRT folks are adamant to push their east const preference into every Microsoft customer.
If you enjoy editing IDL files without tooling support and merging generated files by hand, please do.
Even today no other compiler can fit into WinUI/UWP workflows no matter what, unless one is willing to do lots of under the hood workarounds.
Office politics driven by people without any consideration for paying customers.
Just because it isn't standardized doesn't mean it isn't stable & relied upon anyway. Clang & G++ are ABI compatible with each other, for example, and more importantly (and relevantly) both libstd++ & libcxx strive to maintain ABI compatibility ( https://gcc.gnu.org/onlinedocs/libstdc++/manual/abi.html & https://libcxx.llvm.org/DesignDocs/ABIVersioning.html )
They don't, but they do keep it stable.
Clang, however, does strive to maintain compatibility with G++. So you can build a library with clang & use it from g++. It strives, and for the most part achieves, to be a drop-in replacement for GCC, which includes ABI compatibility.
C++ is mature, there's a difference. It's definitely not "long in the tooth" as it still has plenty of modern features & niceties and is getting more all the time.
> C++ is really struggling with its identity now that Rust is on the scene
C++'s identity struggles have nothing to do with Rust whatsoever. It's struggling to balance ABI & historical compatibility with moving the language forward, that's about it. And that's just something many mature languages face at some point (see Python3 for example).
Beyond that struggle of supporting legacy code, there's no identity crisis with C++ at all.
> the best argument for C++ is just that it has a larger ecosystem (which says more about how old C++ is than how good it is as a language). That argument becomes less convincing with each passing day.
It's still an incredibly convincing argument. Rust has decent C interop, but it's not especially seamless to do safely. But Rust's interop with C++ is rather complicated. So if you have any C++ dependencies, you're almost certainly far better off to just go with C++. Even if you just have C dependencies and someone else hasn't yet made a crate for it, you might still be better of with C++.
Particularly since many of the Rust crates that bind to popular C & C++ libraries are not owned or maintained by the library in question. So you're entering into a bit of a sketchy maintenance world. And there's already been churn & abandonware here, for example the various projects (including one from Mozilla/Servo!) to bind to libpng that were then later all abandoned, and you're supposed to migrate to rewrites of PNG support in Rust like LodePNG. This is not ignorable churn & risk.
It can be fun, and if it's a hobby project absolutely go for it. But if you're on the clock to ship & maintain something, it's a risk you must consider quite seriously.
However, my overall view is that Rust is the right direction. If I had to improve C++, it would look a lot like Rust: move bounds checking and ownership to the compiler not the template compiler, remove classes, introduce traits and make C++ have concepts that don't require me to type every punctuation symbol on my keyboard to use, const by default.
Getting the compiler to do some of this checking by default is exactly what I want. If we can have an Ada/Spark style subset via Kani, also done at compile time to check correctness, even better.
> It's still an incredibly convincing argument. Rust has decent C interop, but it's not especially seamless to do safely.
I would make this statement slightly stronger. If you interop with C code and there are bugs in C code, all bets are off. You don't get any magical protection because your main routine is written in Rust. Cross-language safety, e.g. C++/Javascript, for example, is a very difficult problem. Here's a discussion of C++ and Rust https://dl.acm.org/doi/fullHtml/10.1145/3485832.3485903 If you want a tl;dr of that paper, it is that memory corruptions in C or C++ code sharing the address space of safe Rust code can make safe Rust code unsafe too. There have been plenty of attempts to study for example the interactions between browser C++ and the various runtimes a browser hosts.
The compact you are making with the Rust compiler when you type 'unsafe' is that you are saying you have verified the correctness of that code: that it is in fact already safe and the compiler should not (probably because it cannot) check it. The same is true of any FFI code you use. However, this is not how many people actually _use_ Rust. Here for example is a 2 year old unfixed segfault in a Rust crate caused by interfacing with non-Rust code: https://rustsec.org/advisories/RUSTSEC-2020-0159. This a) demonstrates the issue and b) demonstrates that random crates pulled off crates.io can contain safety issues for 2+ years with no fix.
C++ is mature ? Every couple of years a new C++ standard emerges which breakes old programs. Qt ? Which version ? 3 ? 4 ? 5 ? 6 ?
It's not an unreasonable burden for what you get in return, but it is still a burden.
That point is absolutely crucial. It's not just implicit, it's also often undocumented. Recently, I tried to call LLVM's ORC2 JIT functions from two threads concurrently – an interface which was designed to be thread-safe [1]. And yet, actually doing that resulted in non-deterministic crashes and failed assertions. Guess it wasn't thread-safe after all... None of the types or function prototypes gave any indication that they weren't thread-safe, not to mention the documentation. And that's an extremely popular open-source project! The reality for most other C++ code looks even worse.
Welcome to the world of any language that the cool guys dont use anymore :-)
C is mature. C++ drinks and sleeps around, but that hardly makes it mature.
So in their model of the world, students who learn Rust would be surprised that there are people who do not use Rust.
Stuff like OpenACC, OpenMP and MPI are expected to be in the box, regardless of the language.
On the graphics space, Khronos, NVidia, Intel and AMD are driving the adoption of standard C++ as compute language.
NVidia now has many key contributors to ISO C++ on their payroll and as shown at GTC 2022, wants to make CUDA the best GPU implementation of such features.
Then on game consoles, C++ is the main language on the SDKs, alongside C# and Unity.
One can naturally use other stacks, like Embark Studios is doing with Rust, but then it is on them to spend development resources on making it work, and deal with possible integration issues.
Beginning students are, by definition, by far the least well equipped to understand what would be most beneficial to learn. That universities fail to step up is a disgrace. Those students who learn Rust instead of C++, or "C with classes", will graduate very poorly equipped for what will be expected of them.
What matters is whether they come out equipped to do what is needed. Those picking up Rust will graduate finding very few places to use it, and will have to pick up C++ as best they can under much more difficult conditions.
That is absurd, unless in fact you are teaching C++ so badly that they first need to unlearn more than somebody with no experience with it needs to learn starting from zero.
This cycle has played out for C++, Java, Ruby, C#, Python, Golang… and everything else. What makes Rust a hipster fad, but these languages weren't?
Rust might not fizzle. It is too early to say. Ruby seems to be fizzling after what seemed like a promising start. C#, as a walled-garden language, does not even count.
Why is Rust preferred though? Is it "fearless concurrency" (or whatever the old tag line was), a more modern stdlib, or is it Rust's tooling with `cargo` rather the shitty tool-scape of C++'s make/cmake/meson and vcpkg/conan and whatever else.
Things just work, and that is a rare experience.
Yes.
> a more modern stdlib
Yes.
> is it Rust's tooling with `cargo` rather the shitty tool-scape of C++'s make/cmake/meson and vcpkg/conan and whatever else
Yes.
I don't mean to be flippant. The entire experience with Rust is just so much better than with C or C++, and that's even before you get to the actual language.
But it is (for the most part) C++ that pays the bills.
If we're thinking about University students it's very likely the ratio is even smaller by the time they graduate and are looking for employment. Rust jobs grow, there are lots of new Rust codebases, and they need maintenance people just the same as C++. You may have fewer mysterious unsafety bugs to fix but your customers still have changing requirements, other people's APIs still change, so you aren't going to go hungry any time soon programming in Rust any more than C++
And if we're talking about in-house training, whatever they are teaching pays the bills, the employer isn't sending employees to learn how to do stuff they've no use for, otherwise they'd just give you indefinite leave on full pay.
Brendan Eich's slapped together Lisp clone with semi-colon language syntax is paying the bills more than Bjarne's "What if C but with a kitchen sink of extra stuff bolted on more or less at random?" language. They're both terrible in different ways, but at least Brendan had the excuse of a hard deadline.
1. https://insights.stackoverflow.com/survey/2019#technology-_-...
2. https://insights.stackoverflow.com/survey/2020#technology-pr...
3. https://insights.stackoverflow.com/survey/2021#technology-mo...
Rust will soon be wholly as complex as C++; or it will fizzle. It could still do both. It would be disappointing if Rust were to fizzle unless it is supplanted by something better.
We should (but the C++ committee too often does not) distinguish between additional complexity to serve some particular purpose, and constant greenfield development done solely because it was easier to burn some more forest than deal with the mess left by the previous occupants when renovating. This latter practice is of course unsustainable.
It is also good to distinguish complexity which matters to the implementer (for C++ this may represent maybe three groups - LLVM, GNU and MS - all of which have committee members) versus that which matters to ordinary programmers.
The novice Rust programmer in 2022 sees only that arrays work as you would expect -- the apparent complexity is quite small. You can't grow or shrink them (use a Vec) but you can mutate them (if you asked for a mutable array), you can iterate over them like other containers, they have the affordances you'd expect from a slice type (e.g. you can sort them, and if they're sorted you can do binary searches, that sort of thing) and so on.
In fact arranging this was pretty complicated, const Generics are involved for example, which Rust 1.0 did not have. But this novice programmer needn't care why arrays work, they just do, it's very simple. Compare the C++ novice who will be told that only some array features work, and they're not a collection, and they also aren't really a slice, and the affordances you'd want are missing, the C++ novice must carry around a pretty complicated understanding, all in order to simplify implementation for a handful of people.
Rust also has a third distinction of complexity, where it matters whether the complexity is exposed in safe Rust. For C++ the pointer provenance problem affects everybody. Finally resolving this long-standing hole in the C++ standard would significantly increase complexity for the language, and every C++ programmer may be at risk from the consequences. In contrast, safe Rust programmers needn't even be aware that provenance is a thing, only the nomicon explains that of course pointers aren't really just addresses because only in unsafe is management of provenance your problem.
Const generics, what C++ calls non-type template parameters, is a recent and fundamental complexification of Rust. Libraries using them, as always and ever for all features and all libraries can be simple, but a person coding a library that would benefit from the feature is in no way insulated from it. Async/await, similarly. There is no end to those. They add complexity, but solve real problems. Novices can be shielded, but professionals cannot be, because the features are exactly those needed by the professionals.
C++ chooses to add wrinkles, upon wrinkles, upon yet more wrinkles so as to produce a fractal of complexity that serves nobody, not novices, not professionals, not even the implementers benefit in the end. In the short term it was an administrative convenience I suppose, but it can hardly have been difficult to foresee the consequences.
In my own experience, it's allowed me to write software that pretty much doesn't have bugs, and I say this as a 25 year veteran of being an engineer. The extra bits of complexity that are in the language allow me, the developer, to not have to write code to deal with that complexity in my own projects. It's handled for me.
Rust's primary competition is C and C++. It compares favorable on the overwhelming majority of axes against either. Not every one, but most.
You will know Rust is beginning to catch up when people complain about it as much as C++.
Not everybody needs the expressive power of C++. Certainly most Java, Go, and C coders who have no inkling of most of what I rely on C++ for, or even for what many rely on Rust for. But for those of us who do, there is simply no contest. Nothing else will do.
It's probably of the order of a year out.
The full quote is in reference to popularity. Which if you look at the stack overflow developer survey is definitely true
Wishing is totally allowed, but is not a good look.
SerenityOS is addressed elsewhere in the comments here, so I won't repeat myself. DRY is a maxim, right?