I enjoy Rust, but it's not that simple. It really is more work to accomplish certain tasks in Rust than many other languages even when what you're doing is safe.
The narrative that "Rust isn't hard" is getting tiresome, and I say this as someone who writes a lot of Rust. Let's be honest that Rust can be harder than many other programming languages in many ways, but those of us who use it believe the upsides and tradeoffs are worth the minor to moderate increase in difficulty.
Pretending Rust is easy just sets beginners up for disappointment when they get into Rust and realize it wasn't what they were sold. Or worse, they start doubting themselves when they encounter the hard parts because Rust fans were busy insisting it's all easy.
Writing widely useful, performant, reusable, correct and stable libraries is very hard. Rust is the easiest language to do that in.
If someone says programming in another language is easier, it's because they are not attempting (or are failing) to do one or more of those things. Those things are not always important so that's fine, but a lot of programmers (myself included) have this dream of being able to solve a problem once rather than over and over again, and that's why Rust appeals to us, and why we argue against Rust being hard - because it's actually easier for our particular usecase, but clearly not everyone has that same usecase.
> Rust provides very substantially less support to library designers than C++ does. Anyone creating ambitious libraries finds Rust a big step down.
Before I used Rust my main language was C++ where I specialized in writing libraries, and this is a ridiculous statement.
It's only recently that the C++ standard library has gained enough functionality to do even some basic things in a portable way, so you're relying on other libraries to provide that, yet there is no standardized way to declare dependencies on those other libraries. There is no module system to ease structuring library code. There is no hygiene - a ton of stuff you include will just pollute the global namespace. There is no standard way to version your code. There is no standard way to update a library. Everyone uses incompatible string types - seriously, if you think Rust has too many string types, wait until you find out that every freaking C++ library represents strings differently, sometimes using the same types though! There is no standard place to publish libraries. Even basic language types like `int` differ massively from platform to platform, or even between different compilers on the same platform. Each compiler's preprocessor behaves slightly differently. The programmer must manually forward declare their functions, types, etc, and the rules are different for inline/templated code. All code is unsafe, and yet the rules for what constitutes UB are informal at best. (Whereas in Rust, the rules for UB are also not fully defined yet, but this is only relevant for the minority of your code which is not safe). All experienced C++ programmers think in terms of lifetimes, and yet cannot express this through the type system, so this must be documented informally. There is no standardized coding style or format.
I'm going to stop now just because I'm bored but this list could go on for a very long time...
What makes UB in C++ is spelled out in its International Standard; there is no specification for Rust, just an implementation. It has been many years since C++ preprocessors differed notably from one implementation to the next. Nothing in C++ is global except what you choose to make global.
In fact today C++ lifetimes are expressed in the type system, and this is an example of what C++ enables a library to provide.
If, working as a library designer, Rust was not a big step down, you were neglecting to provide users of your libraries much of the value you could have offered. Your remarks suggest that libraries you delivered were closer to C than C++.
> What makes UB in C++ is spelled out in its International Standard
That's not true, the standard only specifies that some things are UB, it is not exhaustive, nor is it unambiguous. Furthermore, no current C++ compiler is compliant even with those parts of the standard which everyone agrees on (eg. see proposals to introduce a "bytes" type to LLVM to resolve known miscompilations). There is work to improve this, such as defining an explicit memory model, but Rust is ahead of C++ on this.
> In fact today C++ lifetimes are expressed in the type system, and this is an example of what C++ enables a library to provide.
Do you have an example of this, or are you talking about using smart pointers? Smart pointers are about ownership, not lifetimes.
> Your remarks suggest that libraries you delivered were closer to C than C++.
Most of my remarks were about consuming other libraries from my library, which is not something I have control over. Sure, I can use smart pointers and other modern C++ features in the API I expose... That doesn't change any of the points I mentioned.
> Pretending Rust is easy just sets beginners up for disappointment when they get into Rust and realize it wasn't what they were sold. Or worse, they start doubting themselves when they encounter the hard parts because Rust fans were busy insisting it's all easy.
You could be describing me. I recently convinced my boss to let me write a server in Rust for the safety, speed, etc. After being two weeks overdue, I threw it all out and wrote a working version in modern C++17 in an afternoon. Of course part of the issue was language familiarity, but I think also what I was trying to do was objectively harder in Rust in many respects, and the ecosystem of crates was less mature than battle-tested set of libraries I was using in C++.
At the end of the day I want to ship code and move on to the next project. Rust wasn't helping me there.
Rust isn't hard for the things you try to solve in Rust.
The author compares Rust code with Go code. Those two languages serve entirely different purposes, with entirely different mechanisms behind it.
Go does what the author does with ease because the runtime fixes all the complicated parts for you. You tell Go what you want and it'll try to solve all the memory management/threading/memory safety issues for you, usually succeeding.
Rust doesn't do that. Rust expects you not just to tell it what you want, but also how you want it to happen.
I think comparisons between Rust and Go/C#/Java are what will really trip up a beginner. Rust has a lot of nice features found in higher level languages, but it's decidedly not a higher level language. Rust operates in the space of C and C++, where a small mistake can cause memory corruption no debugger will ever be able to unravel, but where a well placed byte of padding can accelerate a program by as much as 30 percent.
I think the difficulty in Rust lies in that it will enforce correctness. Competing languages are less strict about that, especially when it comes to threading. You tell them a piece of memory is safe to use across thread boundaries and they'll believe you, and most of the time you can rely on race conditions not screwing you over perfectly well. A C program can be short, fast, and clear, as long as you leave out the error checking and resource management in case of failures; with Rust you often don't get that luxury. Writing correct code is a slow, tedious, painful experience, and in Rust you'll have to live with that pain (unless you throw around unsafe{} everywhere).
I believe that teaching programming should follow a bottom-up approach, but many others disagree. If you've dabbled in assembly, done some multi threaded C(++) and experienced the challenges in low-level code, Rust should be enjoyable enough to learn after cursing at the borrow checker a bunch of times. If you're a top-down learner, though, you'll run face-first into low-level problems and their complexities for seemingly no good reason.
In fairness, for the kind of software that C++ is particularly suited, the idiomatic software architecture is thread-per-core, which has the distinction of being almost entirely single-threaded at the code level. Race conditions aren't a meaningful concern because data virtually never crosses thread boundaries. The bigger issue, particularly and mostly for C code, is object lifetime management.
If maximum performance, whence thread-per-core architectures originate, is not the objective, then GC languages start to become more attractive and C++ may not be the right tool for the job. And in those cases, Rust may not be either.
You're not wrong, but the crux of the problem described in the article is that Rust's object lifetimes are very hard to get right (even for the people working on the compiler) when working with cross-thread code.
I'm no professional Rust dev but I wouldn't have written the code like this; I know Rust isn't particularly suited for this style of callback mechanism and I know not to try and force this paradigm into Rust the same way.
For example, I think the author would have had a much raiser time if instead of passing async futures, they'd use channels or some other message passing mechanism in combination with a bunch of blocking threads to communicate events. Such a mechanism would also translate into Go quite easily (less so for other languages, though).
This example was deliberately picked to show a complex problem with writing Rust. I don't think this represents a challenge you'd face very commonly if you were programming Rust all day, at all. It's not bad criticism, but it appears to imply a much wider problem than there really is, in my opinion.
I don't really know what kind of programs require such an elaborate callback system commonly enough where it even makes sense to use Rust. C#, Java, and Go are fast, easy to write, and each have libraries to do almost anything you want. That 10-30% speed boost you can achieve with well-written Rust is probably not really worth the effort, especially with upcoming AOT compilation features in C#.
Rust isn't a solution to all problems, and neither is any other programming language.
> I think the difficulty in Rust lies in that it will enforce correctness. Competing languages are less strict about that, especially when it comes to threading.
Enforcing correctness at compile time is not the only way to insure correctness.
Some do enjoy solving language puzzles (so choose Rust) and some prefer thinking before coding and prefer solving design puzzles. I personally prefer that latter, as the 'hard' problems are intellectually interesting, solving them is satisfying, and over the years the design lessons build upon each other. At which point you don't need a Mommy Dearest Compiler to ensure correctness.
This is the "don't do anything wrong" model of software development, and while it works well for some, we have enough experience as an industry to know it doesn't scale.
Crucially, it's hard to prove whether or not you've actually solved whatever design issue you wanted to overcome. Such a proof usually would entail some sort of analysis of the program as written (because it may actually differ from your design). To perform this analysis, you may want to annotate the lifetimes of the various objects as they are declared, so that you can track (for example) that some memory is not accessed after it is freed, or any other number of issues.
This lifetime analysis as you would imagine can be very tedious and complicated, so you would perhaps want to automate the process. And that's essentially why Rust's borrow checker exists. It's almost inevitable that it should exist imo. Seems completely obvious after the fact.
> All type systems will have meaningful and true propositions which are apparent to the programmer but not yet to the language team... Some of what the author is complaining about matches my conversations with people who aspire to be Rust library authors — that you're often trying to hijack the type system because you <do> actually know better.
Rust catches some problems (like data races and use-after-free). Safe Rust translated into correct C++ is still correct. Rust also fails to catch some problems (like preventing out-of-bounds indexing at compile time); admittedly idiomatic C++ fails to catch bounds errors at runtime. And when encountering problems that Safe Rust cannot solve (like the generic lifetime quagmires in the original post), C++ often makes it possible (and easier than esoteric programming languages like Unsafe Rust) to solve the problem correctly in the current situation; though admittedly, ensuring you haven't missed any UB cases, and validating that your assumptions don't break later on, is difficult (Unsafe Rust is better at marking unsafe code for future readers).
> Enforcing correctness at compile time is not the only way to insure correctness.
> some prefer thinking before coding and prefer solving design puzzles
Yeah, you just need to guarantee that person working on it, considered all edge cases, had uninterrupted time to think, thought about how the edge cases interact, didn't make a single mistake, wasn't sleepy, under influence of substances, and perfectly wrote it into the program without a single semantic error (off by 1).
Easy.
That's why I code in Malbolge Lisp CodeGen that outputs Brainfuck.
If you can solve all your problems by thinking before writing code then you will never see a compiler error, unless you see a compiler bug. If you're solving your problems beforehand by thinking about them but end up solving language puzzles, you clearly haven't thought enough.
If the benchmark is "projects that are known for idiomatic C", such as Redis or Sqlite, we know that even they introduce memory corruption errors that lead to vulnerabilities every now and then. You're not better than them.
>I think comparisons between Rust and Go/C#/Java are what will really trip up a beginner. Rust has a lot of nice features found in higher level languages, but it's decidedly not a higher level language. Rust operates in the space of C and C++, where a small mistake can cause memory corruption no debugger will ever be able to unravel, but where a well placed byte of padding can accelerate a program by as much as 30 percent.
I agree. But if you use Rust for web programming, it is fair to compare it with C# or Java.
On the other hand, C and C++ feels easier to read and write, a bit more productive than Rust.
So the question is vs C# and Java: is the performance worth the pain and loss of productivity?
And vs C and C++: is the guarantees made by Rust worth the pain and loss of productivity?
I'd argue that in some cases the answer can be yes, while in others it ca be no. So, there's no universal good or bad choice, it really depends on project, team, budget and many more.
I don't know why you'd possibly want to use Rust for web programming, to be honest. When you add a full stack of databases and entities, Rust barely becomes faster than ASP.NET or Spring Boot. I messed with it for fun, but I don't think I'd pick Rust as a web server language any time soon.
The only reason I can think of is the WASM space, which Rust lends itself very well to, to reuse the same entities and data structures in the front end. Then again, you'll end up writing a terribly bloated web UI and other languages have similar bindings.
I think for new projects where C++ makes sense, Rust probably makes more sense. There are some edge cases (if you expect to be operating on trees in memory, for example, or if you're interfacing with libraries written in other languages) but I think Rust is generally better for such system tools. That assumes that you have in house Rust devs, of course; if you're a C++ shop, you'll have to teach everyone a new language before the switch makes sense.
The C(++) crowd is difficult to teach other languages because they, more than any dev group I've encountered, seem to have a larger amount of vocal people who think their code is perfect, they won't ever produce bugs, and all those compiler errors warning about failing edge cases are unnecessary because they know best.
I'm in the same boat, I spend a bit of my working day writing in Rust .. it's a great language but it's very cumbersome and I find it suffers form poor ergonomics, like it's annoying to type
I find it enjoyable to type for the most part. Explicit type casts aren't a treasure or anything, but then I think about why I am doing it and I can't really complain.
I feel you, but, my experience differs in the end I guess. To be fair turbofishes we're something I discovered by intuition, so maybe I'm a lost soul.
Programming should be hard only if the problem you are trying to solve is hard. Creating an boring CRUD app shouldn't be hard. Predicting with a good degree of success the market trends for stocks and options should be hard.
All type systems will have meaningful and true propositions which are apparent to the programmer but not yet to the language team. Choosing a typed language is choosing to have a relationship with a living & evolving team/ecosystem, one which can improve over time in their ability to express and prove propositions or provide ergonomic abstractions.
Some of what the author is complaining about matches my conversations with people who aspire to be Rust library authors — that you're often trying to hijack the type system because you <do> actually know better.
Im not so sure. There are a lot of error states that won't happen in production ever with other languages because the language/libaries handle those conditions gracefully without you needing to care. Rust pushes this to the edge, _which is good if you want that contol_, but is a cost you have to pay.
This right here. Rust is a low-level language hat makes a whole dimension of implicit knowledge explicit.
This is a very good thing, but if you are a programmer that is used to "copy paste, and then it works". You will have a very, very, very bad time with it. Rust forces you to think about memory. In an age where dynamic typing is so prevalent this seems like a fading art.
The narrative that "Rust isn't hard" is getting tiresome, and I say this as someone who writes a lot of Rust. Let's be honest that Rust can be harder than many other programming languages in many ways, but those of us who use it believe the upsides and tradeoffs are worth the minor to moderate increase in difficulty.
Pretending Rust is easy just sets beginners up for disappointment when they get into Rust and realize it wasn't what they were sold. Or worse, they start doubting themselves when they encounter the hard parts because Rust fans were busy insisting it's all easy.