I think most folks would agree that Rust is an "acquired" taste. You need to kneel to the borrow-checker and develop something like a Stockholm syndrome before you find the language delicious.
> You need to kneel to the borrow-checker and develop something like a Stockholm syndrome before you find the language delicious.
That was not my experience at all. I liked the language from the get-go. And the more i dug into it, the more i found to like. I really appreciated the design of Rust's iterators, and traits, and general language design, way before i stumbled into any major issues with the borrow checker.
I feel like this depends very much on the programming style and probably on the domain. But i found that programming in a mostly "functions and simple data structures" way, passing things down to other functions that need them, and just processing data instead of "modeling the domain" into mutable and confusing "objects", i didn't really come across many lifetime-related issues that the borrow checker warns about.
And the few that i did found, they were actually errors on my part, and Rust detecting them was quite helpful. Things like trying to mutate a collection while also iterating it.
OOP has many short comings (enough that I would say it has been the single worst design paradigm that afflicted this industry), so Rust felt like a breath of fresh air to a c++ dev. However, many basic patterns that are employed when you are building embedded software are just needlessly difficult. Zig is the better C, and it’s just the better language for getting stuff done with total control. If I need to be high level with more I’ll reach for something else (c# provides all the optimisations I need if I’m not bit bashing). Rust feels like it has this one great trick, no gc and memory safety, but nothing else to offer.
The problem I have with what I call "bondage and discipline" languages is that while it has value making sure the code is correct, it is not pleasant (for me) to work with.
Sometimes I just want to try out stuff, I know there is a bug, in fact, it breaks everything, but that's exactly what I want, I will revert it once my little expertement is complete. Strict languages will refuse to run anything unless you make sure that code follow all the rules, which is useless work as you know it won't stay (because it breaks things on purpose).
It is not just Rust, it is just that Rust has a lot of that. There is also C++ not allowing me to access private class members, or Python being annoying with its indentation, these are all good things for production code, but it gets in the way of experimentation.
Combine this with slow compilation times and it makes Rust not very experiment-friendly, which I think is the reason why many people don't have a good time with it.
As I understand it, Zig is more permissive and also has fast compilation as a design goal, which I think is a big contributing factor in making it more "pleasant".
You're right that rust is not experiment friendly, especially since it doesn't have a repl. I guess I must be into BDSM because my go-to language for experimentation is Haskell
Zig can also be annoying. For example, you cannot just ignore the return value, even in debug builds. Playing around with slices, c strings, arrays is also annoying in comparison to C.
Yes, that's a tradeoff, bondage and discipline languages like Ada and Rust are popular in some fields for a reason.
What I like the most is a middle ground: have a language that it permissive when it comes to generating code, but be strict with warnings. Again, problem is that too many people don't care about warnings. Way too many times, I had people call me to investigate their bugs, see a warning, point it out and tell them "here, that's your bug".
An interesting thought experiment would be a language/toolchain that would be permissive when generating debug builds, but hard-required warn-free to generate an optimized executable.
Put Claude code on top of it and now you have prototypes of what you have in mind written pretty much instantly and they are suitable for reshaping into production later if needs to.
Lol, not that it's any good, but here is a pretty much purely vibed zig NES emulator that I let Claude work on: https://github.com/RAMJAC-digital/RAMBO It renders something.
I'd guess that in 99% of cases, if the borrow checker is a problem for you in Rust then you are likely not ready yet for C or Zig, particularly when you need to work in a team where mainatainability by others is critical.
There are some cases the borrow checker requires you to go through hoops for but I see that as a win for adding friction and raising visibility of weird patterns.
And yes, there are cases that can't be expressed the same way,
It’s also true that people overestimate how often the “weird” patterns are needed. 9 times out of 10 it’s the programmer who is missing something, not the borrow checker.
That has not been my experience with it, but I understand if it is yours. I have often seen people use convoluted or slow patterns to satisfy the borrow checker when something slightly un-kosher would have been simpler, faster, and easier.
Using "unsafe" for things that really need it structurally is incredibly unwieldy and makes all your code a total mess. A single instance of "unsafe" is clean and fine, but if you want or need to use patterns that do not follow the religious "single ownership known at compile time" dogma, you end up spewing "unsafe" in a lot of places and having terribly unclean code as a result. And no, "Arc" is not a perfect solution to ths because its performance is terrible.
I encourage you to write a doubly linked list in Rust if you want to understand what I mean about "un-kosher" code. This is a basic academic example, but Rust itself is the rabbi that makes your life suck if you stray off the narrow path.
I write a decent amount of system-level software and this kind of pattern is unavoidable if you actually need to interact with hardware or if you need very high performance. I have probably written the unsafe keyword several hundred times despite only having to use Rust professionally for a year.
> Using "unsafe" for things that really need it structurally is incredibly unwieldy and makes all your code a total mess. A single instance of "unsafe" is clean and fine, but if you want or need to use patterns that do not follow the religious "single ownership known at compile time" dogma, you end up spewing "unsafe" in a lot of places and having terribly unclean code as a result.
It's only “unclean” because you see it that way. In reality it's no more unclean than writing C (or Zig, for that matter).
> And no, "Arc" is not a perfect solution to ths because its performance is terrible.
There's no “perfect solution”, but Arc in fine in many cases, just not all of them.
> I encourage you to write a doubly linked list in Rust if you want to understand what I mean about "un-kosher" code
I've followed the “learning rust by writing too many linked lists” tutorial ages ago, and I've then tutored beginners through it, so I'm familiar with the topic, but the thing is you never need to write a doubly linked list IRL since it's readily available in std.
> this kind of pattern is unavoidable if you actually need to interact with hardware
You need unsafe to interact with hardware (or FFI) but that's absolutely not the same thing.
> I have probably written the unsafe keyword several hundred times despite only having to use Rust professionally for a year.
If it's not a hyperbole, that's a very big smell IMHO, you probably should spend a little bit more time digging into how to write idiomatic rust (I've seen coworkers coming from C writing way too much unsafe for no reason because they just kept cramming their C patterns in their Rust code). Sometimes, unsafe is genuinely needed and should be used as a tool, but sometimes it's not and it's being abused due to lack of familiarity with the language. “Several hundreds times” really sounds like the latter.
Well said! Having a mental borrow checker running in background certainly helps a lot when coding in Zig. What also helps is that Zig safety checks generally catch lifetime bugs at runtime nicely. E.g., undefined memory is set to `0xAAAA`, which, if interpreted as a pointer, is guaranteed to be invalid, and fail loudly on dereference.
Having a well designed type system is a as addictive as sugar.
Also, Rust has a bunch of annoying warts but the borrowck ain't one of them (unless you're trying to write a linked list but it's not really something that happens IRL).
The funny thing is, in a language like Zig where memory allocation is explicit, linked lists are way more popular, just like they were in C. What happens IRL depends on your environment. :)
Linked lists are not popular in zig though. There are pages and pages of discussions about how linked lists are almost universally slower than an array. Zig devs are borderline obsessive about cache efficiency.
Linked lists are slower if you iterate over them fully, however, that's not the only use case. Removing something, while keeping things in order. That's an O(n) operation on vectors, unless you have some kind of fancy segmented structure. Adding might need an allocation, even ignoring the option of failure, that's an unpredictable delay. On the other hand, linked list insert is O(1) and has no allocation involved. These are the properties people use linked lists for.
Even if we set aside the performance consideration of using a linked list, if you have to re-implement a linked list, there's something wrong with your language ecosystem.