Venturing into the holy lands of Rust, we often hear the chants of fearless concurrency, memory safety and zero-cost abstractions, but truth could seldom be so lofty. These chantings are as hollow as the priests themselves.
There's nothing such as zero-cost abstractions. Any abstraction, no matter how transparent, brings both a performance and cognitive penalty. It applies not only to Rust, but also to carcinogens like C++.
Cognitive penalty is especially applicable to bindings that dramatically change the original paradigm of the underlying library without (properly) documenting it; in that case, source code must be read to ascertain correct behaviour. This needless effort makes abstractions not zero-cost, but sometimes more cost than if you had just used the library without the binding.
I've seen Rustaceans act with hostility towards unsafe code. And, I understand that often there's no need for unsafety, but in certain cases it has been shown in the past that performance is achieved through unsafety.
And not just that, sometimes unsafety is a necessity, such as when dealing with FFI (Foreign Function Interface), which is foundation of bindings. Bindings exist because not all code has been rewritten in Rust, much to dismay of the Rust community. Many take safety for granted without considering the circumstances where the unsafe code used to implement the safe interface may non-deterministically malfunction, which is the case with a lot binding libraries out there. Debugging this requires to scrutinize the source code as mentioned before.
People hailing to a background in low-level languages (e.g. C or FORTRAN) find Rust rather hard to understand at first, because in those languages the norm is to manually manage memory or resources (sockets, files, etc). However, Rust is based off of RAII and ownership; i.e. a resource may have a single owner, and it can be mutably borrowed once, but immutably borrowed an infinite number of times.
Ownership model unfortunately isn't silver bullet for all problems regarding memory safety. There exist valid cases, when a reference is semantically immutable, but it requires mutability for bookkeeping; e.g. Rc<T> or Arc<T> — smart pointers for a heap-allocated reference-counted object; former one for single-threaded and latter for multi-threaded scenarios, using Cell<T> for counting references. Additionally, Arc<T> is allowed to be shared between threads1, because immutable (read-only) references could be sent between threads.
What rustaceans call concurrency is actually parallelism. And, parallelism is no child's play. It might be easy to spawn a pool of threads to do a perform a task, but whether if it's actually beneficial, or worse yet, detrimental to performance. The topic of parallelism is complicated and hard to get right, and it demands experimentation, profiling and optimisations. The algorithm that is designed to do the task in a sequential scenario, may often be suboptimal in a parallel scenario, and must be modified accordingly, given that it's even possible.
Getting trained in a rayon bootcamp isn't going to get you past making toy programs. Life is more harder than that.
Similar to many languages (e.g. Python), Rust has adopted a model of explicit cooperative multitasking. Rust's futures are a hybrid version of Python's coroutines and futures, but ultimately represent a deferred computation whose result will be available “in the future.” Python's coroutines are generators in disguise; as generators enable bidirectional communication with the event-loop. A coroutine suspends execution at an await-point, and is then resumed when the awaitable resolves. Generators store state (local variables with their values) of the execution in a generator frame.
While this is no problem for Python — because all data is reference-counted — it becomes a problem for Rust, as storing the state of the future, referring to it an a status field (e.g. an enum) is not possible unless either the values are reference counted, or raw pointers are used to make a self-reference. Yes, raw pointers are necessary because the ownership model is (again) not flexible enough to accommodate for self-referential types (i.e. our future).
Here's the irony, the harbingers of fearless concurrency don't consider raw pointers a safety hazard here; raw pointers, however, are the root cause of all memory safety bugs. In this case, if a self-referential structure is moved, its raw pointers becoming dangling. Where's your safety now?
To solve this problem, sages who jotted down the holy scriptures of rustonomicon came up with a brilliantly stupid idea: introducing a new type Pin<P>, instead of just implementing move constructors like sane people2. Pin<P> is often misunderstood, because it's very non-intuitive; essentially it's just a wrapper over a smart-pointer (Rc<T> and Box<T>) or a reference (&T or &mut T), enforcing that a mutable reference to a pinned can't obtained safely. However, who's stopping a Tom, Dick or Harry vibe coder use a type like Pin<Box<RefCell<T>>> and invalidate this guarantee safely? Pin<P> only prevents you to obtain a mutable reference to RefCell<T>, however a mutable reference to T could be obtained safely; i.e. legally do a crime.
Sure, you don't directly implement Future<T> all the time, but who's there to guarantee that some profoundly stupid programmer this sort of atrocity behind your back, and the compiler silently let it slide?
The demigods of Rust, equipped with their omniscience, had dictated that dynamic linking is inferior to static linking, hence all Rust projects must be compiled along with all its dependencies must be compiled along with it.
Not having a stable ABI, it's often impossible to link a Rust library (.rlib) compiled with one version of rustc with another, because of ABI differences. Demigods might have all powerful machines, constantly whirring and buzzing, constantly compiling Rust code, but we the mortals don't have such luxuries, so we waste millions/billions of clock cycles compiling the same code, that could just have been compiled once.
The Rust community doesn't teach people to consider tradeoffs, and forces a singular authoritative opinion. The whole community thrives on meaningless politics3—always, right from the start. And, god forbid you make a remotely politically incorrect opinion, the entire community would cannibalise you!
Their appeasement policies towards non-binary people is apparent. I personally don't hate any LGBTQ, but I hate it when a certain group is pandered for vote bank politics. It's the reflection of late-stage capitalism prevalent in Western countries, its associated neo-liberal contemporary woke pandemic, and rise of social justice warriors patrolling the social media sphere, voluntarily taking the “responsibility” to ensure it's a “safe place for all” and all those first-world problems that hardly matter.
Checkout this blog by Arija A. (https://blog.ari.lt/b/rust-bad-ii/). She has done a great job of pin-pointing exactly where the deficits of Rust lie. Fuck Rust. Peace.
This is a very fair point. As can be seen in things like DOOM, yes, performance is achieved through extreme unsafety. I do also think the ABI is a problem. Something made for Rust 1.7x.x may not work in 1.8x.x and vice versa. I also do hate the politics around Rust. There truly is a problem when the entire community thrives on criticising and bringing down others with opinions only slightly different (aka the complete opposite of freedom of opinion & speech).