My negative views on Rust
chrisdone.com
chrisdone.com
My basic bot was done in about an hour. One of the guys took about an hour to try and compile an outdated, abandoned crate for interacting with the Matrix API. I recall he then had to write his own using async/futures. It took him about 6 hours and he was working well into the early morning after we'd already left. The resulting code was hideous - a slurry of nested closures, ?s, unwrap()s everywhere. The compiler was throwing demonicly complex compile errors at him and he was doing whatever he could to get it just to compile. Bearing in mind he was not new to the language.
No, I'm not arguing that Python is better, these languages clearly have a different purpose. I'm arguing that if it takes a really smart guy like him more than 6 hours to produce something of that quality in Rust then... it's not for me. I've already tried it and development with it is just slow and I don't like several syntax decisions. Not to mention the crate ecosystem which is primed to be the new npm disaster.
Rust is the wrong language for a hackathon, but hackathons are an extreme edge case.
Most devs work on large, aging code bases built by other people. Rust and similar languages make that work much less error-prone. Who cares if it's not good for a hackathon?
I'm not sure where this 6 hours for matrix bot thingy comes from. A better comparison would be to write something from scratch or use comparable frameworks.
I’ve always written default-efficient code myself (mostly in C++) and using Python exclusively and over a longer time period for data wrangling was an educational experience. It feels quite liberating to just write code without having to think at every point about efficiency and memory.
- Rust isn't good at writing scripts (one-off code)
- 30 year old language with massive popularity has better libraries than a relatively unknown 6 year old one.
- Rust async isn't mature enough.
Do you need type safety or static type checking? Are you dealing with concurrency, or need the performance that manually managing memory provides? Do you want safety when doing low-level operations such that the compiler can eliminate many classes of mistakes?
Rust might be the right choice.
Are you trying to write a throwaway chat bot for hackathon and you only have a few hours?
Rust probably isn't the right choice unless you're very, very skilled.
the python version is 100x less energy efficient than the rust version. that 5 hours extra work now becomes a bargain.
Not necessarily. If the limiting factor is network access, then the language you write in wouldn't make any difference. You could say you would use less ram with the rust version, but ram is cheap compared to developer time.
how?
Some optimizations become much easier in the actual running environment. You know the hardware so don't have to cater to lowest common denominator, and you know what's actually happening and don't have to guess.
Profile-guided optimizations later showed up to bridge some of the gap, but I think JITs are typically built with a different mind set, so there are some things they excel at.
A funny example is something like a regexp matcher where a JIT might take an actual regexp built on the fly, say based on a user searching for "foo bar", and optimize code for it.
"You Ain't Gonna Need It." --Martin Fowler
I'm not saying don't do it -- in some cases it makes sense say as an open source project. But you can get away with a lot of python code before you have to break out the compiled languages -- particularly if the IO cost is significantly more time consuming than your business logic.
Actually, the quote is by Donald Knuth [0].
Personally, I wouldn’t prototype in Rust either, but OTOH I’m not experienced enough in it.
I'd say building V1 in a rapid development language and then never porting it while trying to scale it up is an even more popular pattern.
There are some massive web applications out there running on hopelessly outdated Django
Not having a GC and not freeing memory by hand is one of the features that makes Rust special. What would be the point of this?
> I feel like Rust is self-defined as a “systems” language, but it’s being used to write web apps and command-line tools and all sorts of things. This is a little disappointing
Why would using the language for non-system programming be disappointing?
I have a feeeling that it has become popular lately to criticize Rust for whatever reason, I guess that's what happens when more people use the language
If I understand this experiment, college students i.e. in average completely inexperienced programmers, where tested. IMHO, this only points to the fact that Rust has a steep learning curve. I can imagine that using javascript instead of Rust would have them finish their tasks even faster, but doesn't tell us that GC in Rust is a great idea.
From the code I've seen, Rust is intuitive when coming from the classical languages like Java, C or JS. It's not lisp or Haskell.
To clarify, I don't intend to come off dismissive. I'm genuinely curious what the scary parts are so I can better plan my own studies.
I still struggle with lifetimes, but I do understand them better.
If the person is an experienced C or C++ programmer they'll probably have an easier time as you need some understanding of ownership and lifetimes to correctly use pointer-likes in those languages. The most likely issue here is that their mental model of ownership and lifetimes does not exactly match the borrow checker's.
If they're coming from a language such as C# or Python, where you don't need to deal with these to anywhere near the same extent, suddenly being forced to reason about it just to pass the compiler can be bit of a hurdle.
I have looked superficially at Rust since I have no use case for me right now and it's truly complex but people downplay the importance of not having GC. There are plenty of languages with GC, for a reason. People use C, C++ or similar for a reason too, not out of masochism.
While Rust may have less issues than other languages in this regard, it doesn’t change the fact that destruction of a “thing” can result in a transitive set of “other things” being destroyed as well, and depending on the state of the program, this set could have a pretty high bound.
Even in a “predictable” language like Rust, a data entry containing a map like structure is going to have a different destruction time if the map is empty vs if it is full.
The other aspect of “predictable” delays in a program completely ignores the runtime issues that can happen at the OS layer, such as swapping/page cache invalidations/migrations between cores of numa regions etc. so it is rarely the case that any program in any language is going to have predictable behaviour. Even trivial things, like the number/size of environment variables user at program startup can have a performance delta.
Anyway, the point is that when programmers talk about “predictable” behaviour in a program, what they generally mean is “invisible performance problems” and so it gets swept under the carpet.
That’s not to say that GC doesn’t introduce variance into measurements, but it is not the only thing that causes variance and many of the good GCs are capable of using multiple cores and can avoid interrupting the program runtime without significant overhead, although obviously tracing collectors will increase pressure on a memory system in a way that explicit/automatic memory management does not.
This is generally an interesting feature of garbage collected languages, and it's often more efficient than manual memory management - but it does come with downsides.
> Even in a “predictable” language like Rust, a data entry containing a map like structure is going to have a different destruction time if the map is empty vs if it is full.
"Predictable" does not mean "always the same". It means that given a map with the same size and fill level, destroying it will take the same time. What's more important is that the point in the program where the destruction happens can be controlled - and, if necessary, moved out of the critical path of something that requires response within a certain bound. This may introduces an overall inefficiency in the program, but if you have software that needs to fulfill certain time bounds, that's the tradeoff you choose. Doing so in a garbage collected language is much harder.
And rust targets environments where (near)-realtime behavior and predictable performance is important, or where garbage collection is not truly possible since allocation isn't even possible (think embedded systems).
> Anyway, the point is that when programmers talk about “predictable” behaviour in a program, what they generally mean is “invisible performance problems” and so it gets swept under the carpet.
I don't see how this is a counterpoint to what I said - programmers blaming other things for performance problem has been a thing like forever.
To put it bluntly: to make writing Rust fast and fun. If you think it's fast and fun right now, you're either very good at it or somehow delusional.
On systems programming front, last time I checked there were two options to make system calls (apart from writing inline assembly), the `libc` or the `nix` crate. `libc` is just a wrapper so exposes `unsafe` C like API which doesn't make for idiomatic Rust code. `nix` was incomplete on lots of front, not sure if things have changed now since it has been a while I tried Rust. So much for a systems programming language.
That some people don't care what makes a language "special", but e.g. like its syntax, ergonomics, ecosystem, and so on.
For example, it is possible to make the borrow checker accept self-referential structures, but nobody has done the immovable type etc work required for that. Which means, for non-trivial data structures, you need to either reach for unsafe or Rc/Arc. I write a lot of very multithreaded code, so for me it's almost always Arc, which means paying for atomics. And since the Drop trait takes an exclusive &mut reference, you must use fugly nested structs to prevent UB if you have shared mutable pointers you need deleted automatically.
I'd recommend learning Rust, but I'd also strongly recommend that you willfully push the limits of the language. Rust needs to be stretched out to accommodate more contemporary programming constructs, and you can help with that. Also, stay away from the core team and organization, they're poisonous as hell. I'd recommend donating to gccrs to help alternate implementations.
On the positive side, you do still end up with an awful lot of zero-cost safe code, and Rust is pretty nice to work in once you actually understand it.
As far as I understand, that should be practically zero-cost on x86, and quite cheap also on ARM - and it's also only paid during acquisition and destruction. I haven't yet caught refcounting in profiling as being the cause of unacceptable performance.
It’s practically zero cost when uncontested. When contested, the instructions with that `lock` prefix are concurrently accessing a single cache line from multiple cores at the same time.
These cache coherency protocols are relatively slow. On some processors, reading a cache line recently modified by another core can cost 300-500 cycles, way more expensive than a cache miss and roundtrip to off-chip memory.
Your requirements are probably similar to this C# queues class: https://github.com/Const-me/Vrmac/blob/master/VrmacVideo/Aud... That library decodes and plays realtime video + audio, both low latency and consistent performance over prolonged use were rather important. BTW that code runs on Raspberry Pi4, CPU performance is a fraction of what you’d expect on modern desktops or servers.
This is a good general point that isn't even about Rust. It crops up a lot in the always ongoing database wars where people keep swearing that one database is fast or slow or new or old.
There needs to be without a thoughtful acknowledgement that there are design trade-offs involved and that appropriateness for the situation is more important than the raw technology. A rewrite making code fast always raises questions about why the rewrite was necessary, what battle-hardened error cases might have been lost along the way and whether the speed-up is attributable to the tech or the skill and intent of the programmer.
And for the systems level programming there is stuff like the current work to make use of Rust and the guarantees it gives in large C based systems like the Linux kernel.
That said that Zig is not a worthy endeavour, it does just not give as strong guarantees about memory safety as Rust works to give. (at least if this article is up to date [1])
[0]: https://github.com/Smithay/smithay
[1]: https://www.scattered-thoughts.net/writing/how-safe-is-zig/
Yes because crates are great IMHO. Warp for low-level web apps with filters is flexible. Rocket for high-level web apps has batteries included. CLAP for command line argument parsing is easy. Serde is superb for serialization/deserialization. Diesel and sqlx make data access efficient.
Is there a learning curve? Yes for sure. It can be hard for new teammates coming from JavaScript/Python/Ruby to learn about Box, Arc, Rc, From/Into, etc. The borrow checker is like a pro coach-- when you work with it, then it makes the coding process much better.
Proponents conflate a specific approach for memory "correctness" checking with some judgment of what is "correct", or not if it doesn't fit in the checker model.
I get a lot of juice out of a solid linked list abstraction in C, it has many benefits such as modest footprint leading to naturally extensible and composable object lists everywhere instead of fixed arrays that have to overallocate and break when it's not enough for some usecase. The lifecycle of the allocations that bind to the list can be opaque, but in it's not opaque to valgrind and leaks are visible without additional in-code instrumentation.
If the valuable uses of that abstraction cannot be handled by the borrow checker, it just means that is a restriction imposed by that checker implementation, it is not telling us that this architecture is not "correct".
Good job for making the front page of HN, but I honestly struggle to know why. (Maybe you're famous?)
Are people writing webapps in Rust? When I look at trending Rust projects and Github what I tend to see are databases and crypto.
That is, a language that has most of the surface syntax and semantics of Rust, except anything related to the borrow checker - instead, it's all handled by a runtime GC. So there is pattern matches, immutability, traits, and so on, in Rust style, but no lifetimes and related syntax.
Let's call it WD-40.
Bonus points if this WD-40 language can also trivially call regular Rust libraries.
P.S. After I've written this, I read the rest of the comments, and half of them are about the same kind of idea...
I think C# fits the bill. Pattern matching: https://docs.microsoft.com/en-us/dotnet/csharp/fundamentals/... Immutability arrived recently: https://nietras.com/2021/06/14/csharp-10-record-struct/
Traits are different however. The language has interfaces for decades, when used as generic constraints they’re free performance wise i.e. don’t compile into virtual calls (also allows to use interfaces on value types without boxing things into the heap), however it’s impossible to implement traits externally like `impl Interface for Type` in Rust.
It should also have the other Rust aspects: LLVM binaries, no MS involved, and so on.
I wouldn’t want LLVM, I like JIT. The binaries are CPU and OS agnostic; sometimes I debug C# code on AMD64 Win10 which then runs on Alpine Linux with ARMv7 and ARM64 CPU. It’s relatively easy to generate code in runtime, allows to beat C performance in some cases, and enables good serialization libraries. Binaries are tiny because CIL is more efficient than machine code.
In modern .NET startup time is pretty good despite the JIT. Also, on modern hardware you wouldn’t want short-lived processes being launching at kilohertz frequency regardless on the language. Because cache hierarchy, you’d want to reuse RAM pages over time as much as possible.
> no MS involved
I think after the complete thing is cross platform and open source, MS involvement is not a huge deal anymore.
Any place to find Rust jobs for experienced dev but new to the language?
We're a Rust shop doing something interesting with NLP and web.
> The worst kind of compile error is the one where the compiler is complaining about something that it generated for you, rather than something you explicitly wrote.
> This can be compared with Haskell’s use of monads, that provide syntactic sugar. The more magic you introduce, the harder it is for newbies to learn and to talk about.
I agree. Too much magic (or in the wrong places - especially when it's a crutch to some language quirk) makes things awful.
Let us know what the article gets wrong.
I thinks that one part of it. More worryingly, I’ve heard the Core Rust committee has a hard time saying “no” to new features which leads to bloat in the language.
- I see 'unsafe' everywhere;
- Async is problematic, colored functions!
- Community is nice, but it won't be forever, RIIR people are annoying;
Conclusion: I prefer GCed languages with default async runtimes.
I write Rust code with no "unsafe", and it's hard. Sometimes you have to go back and do a major redesign because you painted yourself into a corner. Sometimes you take a performance hit.
Some crates with "unsafe" that shouldn't have to have it:
* priority-queue ("Improved performance using some unsafe code in the implementation.")
* serde-json, uuid (takes in byte strings and makes UTF-8, without being strict enough about types.)
* crossbeam-channel (lock-free code which does atomic operations on small structs by coercing them into numeric types for which atomic operations are supported.)
There are a relatively small number of pain points which lead people into unsafe pure Rust. The language needs an effort to hammer them out. (I'm not talking about foreign function interfaces to legacy C/C++code; that's never going to be safe, but can become obsolete.)
Async is problematic, colored functions!
The web backend crowd wants to write Javascript-type code in Rust. This is a bad fit. As I've said before, web backend stuff with huge numbers of connections should be written in Go. Goroutines and garbage collection are a better fit to that problem, because there's only one thread-like construct and it's both low-overhead and preeemptable. The Rust approach complicates code that's changed frequently ("agile", right?). You have to be careful about what's async, what's a thread, what's non-threadable async, and what runs more than 100us without giving up control. Too much engineering is required for routine web server stuff. Go handles that all automatically.
I'd like to see two hard problems addressed in the language: backpointers and lock order.
You can do backpointers with strong reference counts forward and weak reference counts backwards, but now you're reference counting everywhere. If you screw up and mutably borrow twice, you get a run-time error when you call ".borrow_mut()". The borrow checker doesn't look at reference counted items. Single owner with backpointer ought to be analyzable statically, although it's hard to do. The basic constraint is that the call chain can't pass through the same mutably borrowed struct twice. That is, it's loop-free. There are additional issues about keeping forward and back pointers in sync, but those are easier than statically detecting a double borrow.
Lock order is a closely related problem. You can't lock the same thing twice in the same thread. Again, this is a constraint that the call chain can't pass through the same lock twice. Also, locks must be ordered, so that you don't deadlock by locking A, then B in one thread, while locking B, then A in another. Again, a constraint on the call chain.
Both of those are hard theoretical problems, and work is underway on addressing the first one, at least. Not having a sound solution to these problems leads to hack workarounds.
There's other little stuff to gripe about, but those are the big items that lead either to serious bugs or major rework.
Regarding unsafe though. In other non-GC languages (C/C++, say) there's no keyword for `unsafe` as that is the default state. And that's fine. People generally don't have too much trouble trusting library authors to mostly do the right thing. In Rust the places where you have to defer to human judgement are clearly marked, and I see that as a net benefit over status quo irrespective of how much `unsafe` is actually used in one or another library.
GC-ed languages are a different matter altogether. They sit on a different point in the trade-offs graph in my opinion. However, pointing out that the design trade-offs that have been taken in Rust may not be ideal for one or another use case is very much a valid thing to say!
I wonder if there are more lines written about liking and disliking Rust in the world than actual Rust code at this point.
Another “I didn’t read the article but I like writing replies about it” reply.
From the article:
> But it’s used at my job, so I’m sort of forced to have an opinion about it.