P.S. I see in realtime, reloading the post, the number of downvotes received (the score goes up and then ways down with the downvotes). Another problem is the Rust community. It has a very peculiar way to stay in this world.
P.S. I see in realtime, reloading the post, the number of downvotes received (the score goes up and then ways down with the downvotes). Another problem is the Rust community. It has a very peculiar way to stay in this world.
It's definitely fair to argue that Rust is complex. But this doesn't compute.
You don't like Rust because you're less productive with it so you write... C? Rust is so much more productive compared to C it's not even funny, even with its limitations and extra complexity that are necessary for memory safety. Things that take pages of code in C can be done in a single line of code in Rust.
This starts at fairly fundamental data structures. Need a hash map? In Rust you have one available, out-of-box, with state-of-art performance. In C? Implement one yourself. And good luck getting even a fraction of the performance the Rust one has.
Then there is dependency management and a huge ecosystem of libraries. Do you need to, say, encode an AVIF image? (Something I personally had to do.) Easy. `cargo add ravif`, and two lines of code later you're done.
I can keep on going, but I'm sure you get the idea.
I really honestly don't see how anyone can in good faith argue about productivity and say that C (out of all things!) is more productive than Rust. That Rust is more complex, sure. That you don't like it for aesthetic reasons? Fair enough. But not as productive as C? No way. Perhaps this is where the downvotes you're complaining about come from?
The libraries that make C more productive are out there, they are just harder to find than in the Rust ecosystem.
IME C programmers (including myself) separate a language and its stdlib much more strictly than (for instance) the Rust, C++ or Python crowd, mainly because the C stdlib is pretty much useless for anything that's not a simple command line tool (and because of such restrictions, C programmers also often use a whole language toolbox instead of attempting to write everything in C - Python and C go very well together for instance, right tool for the job etc...).
No. I also mean that the language itself is more productive, even without the standard library and the tooling. (Although, indeed, to a lesser degree.)
Sum types. Pattern matching. No need to bother with header files. A module system. Proper generics. AST-based hygienic better macros. Procedural macros. Stronger type system. RAII. A proper string type which knows its length. A proper array type which knows its length. Unicode support*. Async + await support. Support for closures. Ergonomic error handling (with the '?' operator). Proper abstraction for iterators.
Do you want me to keep going? Because I can keep going. (:
Again, this is more complex than what you have in C. And it is harder to learn. But that's not what we're talking about. We're talking about pure productivity here, so I'm assuming on one side you have someone who has mastered C, and on the other side you have someone who has mastered Rust. There's just no contest that the Rust person will get things done significantly faster than the C person, because the language itself is so much more powerful. (But of course it's a lot harder to master Rust, so the calculus might be different if you don't want to master your tools.)
* - part of Rust's core so also available in `no_std` contexts, so I don't see it as part of the stdlib but part of the language.
I would still argue that none of those language features really move the productivity needle a lot into one or the other direction. A good library ecosystem on the other hand does make a difference.
All in all, I would probably add fewer features to C than I would remove from Rust to get close to what I expect from a performance-oriented systems programming language (Zig is actually pretty close to that ideal, but that's a different discussion).
Ah yes, I won't disagree here; macros get very complex very fast. It's one of the more undercooked parts of Rust.
> e.g. I don't really see the usefulness of async-await in a systems programming language
This really depends on the task, but honestly it is genuinely useful. Even in places you wouldn't expect, e.g. in embedded: https://github.com/embassy-rs/embassy
And AFAIK there are plans to use async Rust in the Linux kernel.
> This starts at fairly fundamental data structures. Need a hash map? In Rust you have one available, out-of-box, with state-of-art performance. In C? Implement one yourself. And good luck getting even a fraction of the performance the Rust one has.
I've implemented a hash map in my code. I loved the experience, and I know more about computers and hash maps now that I've done it.
And my hash map was faster than the one in the C++ STL last I checked. I haven't benchmarked it against the Rust one, though.
Yep, everyone at least once should implement a hash map. Hash maps are so fundamental and so often used that it really pays off to understand what makes them tick.
> And my hash map was faster than the one in the C++ STL last I checked. I haven't benchmarked it against the Rust one, though.
The one in C++ is very famous for being really slow, so this is a low bar to clear. (:
Not sure if someone directly compared Rust's hash map to C++'s, but Rust's hash map is based on Google's SwissTable, so you can probably assume it performs roughly the same. Here are some graphs: https://youtu.be/ncHmEUmJZf4?t=2093
There's nothing wrong with the C++ hash-map. It's a different trade-off which makes perfect sense for something that is part of the general-purpose toolbox. See my response above for more details if interested.
Last time I checked, benchmarking hash-maps, or FWIW any other data structure, in the vacuum did not make much sense to me. Microbenchmarks cannot mimic or predict end user workloads well enough, at least in the cases where it's worth it.
That said, each implementation has its own trade-offs and in particular the one from C++ STL opted in for the pointer stability. For that reason alone, which I think is quite worthy, open-addressing conflict resolution is out of the question because it cannot give such guarantees.
With that in mind, if you're ok that a pointer to your key-value pair can all of the sudden be _invalidated_ by the mere operation of inserting or erasing yet another element from your hash-map, then of course, you can trade robustness for a more cache-friendly conflict resolution algorithm. But then again, this says nothing about the potential performance improvement unless you're able to measure it.
Just a random recent example: call an async function for a side effect without caring what its result is, Rust yells at you that the result must be handled "`#[warn(unused_must_use)]` on by default", so you'd think I would just annotate with `#[allow(unused_must_use)]`, but no way, you must store the result in a _ variable: "let _ = my_async_fn().await;". It's a great language, it's just ugly, sorry, not sorry.
[1] https://www.youtube.com/watch?v=rAl-9HwD858&list=PLqbS7AVVEr...
[1] https://doc.rust-lang.org/book/ch18-03-pattern-syntax.html
Fun fact: Google-ing "syntax ugliness" gets as first result "Rust’s Ugly Syntax", https://matklad.github.io/2023/01/26/rusts-ugly-syntax.html
In that case, yes, let _ is the usual pattern. But it’s rare that I ever actually want to use that. Typically I either return the error upwards or just unwrap. But if you truly don’t care about whether the operation was successful you can indeed ignore the error.
There are plenty of pain points with Rust but I don’t really think this is one.
Btw, the article you linked argued against the claim that Rust’s syntax is ugly, and instead claims that Rust’s noisyness is due to its semantics, largely in service of performance (which is why production C++ code is often similarly ugly).
Thoses are really bad footguns.
For me Zig looks like something that could become a new C. They aim for great C binary and source compatibility (both ways - called and callable from C). It’s planned to be small and lean. And avoid preprocessor and UB as much as possible.
C++ is not a bad language at least because lots of amazing soft already written on it, but some people accept it with all of the myriads of complex rules with their own subset and some people totally disagree with this and just want Better(Modern) C with plain and simple rules without hidden complex things which happened behind the scene, and counterintuitive features.
About Rust is everything cool except sometimes it doesn't allow you to write correct code and push you to do things in ways that you don't like. But we are programmers and some part of us is a painter :)
I pretty confident that this would go away if you write Rust for a few weeks.
I remember when first learning C, I struggled with some things too, but then they just clicked. It's probably similar when learning any new language (especially if they add something novel)
Here's why. In spite of having quite a bit of experience with many functional and imperative languages before, and picking up new languages used to be a seamless experience. But Rust turned out to be an extremely frustrating experience. I was feeling like I couldn't tell the compiler to do what I wanted it to do any more. I took it as a challenge, didn't want to give up.
And when the first non-HelloWorld app actually got to compile and work (it was probably IPTrap 2, that implements a userland TCP stack), there was this feeling of accomplishment that I couldn't help myself sharing with the rest of the world.
Fast forwarding a couple years... I don't enjoy Rust any more. I still maintain Rust code, but it's a pain, not something I'm having fun doing.
First, memory safety in Rust is overrated. What it actually brings over GC'd languages is that two threads can't share the same pointer at the same time. Nothing else. If I'm not using threads, it doesn't matter. It's not any safer than PHP, Go or JS, but is far more complex. I won't provide memory leaks nor deadlocks either. To some extent, it actually encourages them (I'm often blindly adding Arc, Box, and Mutexes just to silence the borrow checker, not because they are actually needed).
Next, readability is not great. Too many sigils, and the language got more and more complex and less and less readable over time. I'm frequently having a hard time reading my own code after a while. Even more with other people's code. It often feels like people try to maximize the number of language features being used, so that the source code looks impressive, rather than keep the code basic and simple.
Finally, the ecosystem is very unstable. 3rd-party crates (even essential ones such as for error handling, due to limitations of the standard library) are constantly deprecated, abandoned, or having braking API changes. I gave up maintaining some projects, and keep using abandoned (even with security issues, such as old versions of hyper) with others, because the maintenance cost is too high.
After using almost exclusively Rust, choosing Go for a new project (dnscrypt-proxy 2) was enlightening. There were probably runtime bugs that Rust could have detected at compile-time, but overall, productivity was way higher. Making a change is straightforward, I don't have to think about the implications on the borrow checker. The compiler is fast. Performance is fine, and I can spend more time optimizing the actual algorithms than fight the compiler. Go tells you at runtime, not at compile time, if locks have been forgotten. But otherwise, memory safety is comparable to Rust. The runtime gracefully catches out of bound accesses to slices, null pointer derefs, etc.
I also still enjoy writing C code. It's good to have some freedom and control. But C also has rough edges. UBs, portability, minimal standard library.
Zig fixes many issues that C had, and is the language I really enjoy the most now. It's fast, portable, easy to learn, easy to read, has proper error handling, strong typing, catches out of bound accesses to slices and does checked arithmetic, and, overall, is a joy to use. Not to mention its seamless integration with C.
It doesn't have temporal memory safety, but, like Go, Zig's simplicity helps me reduce logic bugs, and focus more on algorithms than their implementation, overall resulting in better code.
Rust profits from "rewrite it in Rust", since it is more fun. But so does Zig.
Still, I am worried about the ecosystem, since every time I pull a bigger crate through cargo, I have to think of npm and the sad state of JS ecosystem. Also, I have a small, rewrite from python project at work and I am also worried whether it will be easy to upgrade in 2, 3, 5 years? If I write it in Java, it certainly will...
Fully agree on readability. It is not that Rust is unreadable, but a lot can happen in the background and you have to look hard for small sigils, presence of semicolons and blocks.
Otherwise there is Go, Haskell, OCaml, Java, Kotlin, Scala, Clojure, Lisp, Scheme, F#,...
Another possible conclusion is that your program design is just not as simple as you thought it was, and Rust is pointing out why. This drives you to make it even simpler, which is arguably good.
If Redis works then the proof is in the pudding, I guess. But as an average/below average programmer I’ll stick to something more modern when I need that level of control.
But when you start composing things together and juggling the lifetimes of various things, the complexity balloons and it becomes a lot harder to understand "spooky action at a distance"-type things. You might argue that with good structure and discipline it's still not difficult, but the assumptions are all implicit and you have to get into the mind of whoever wrote it in the first place to understand it. And I write a lot of C and still find it tricky when I have to go look at some random C codebase!
Absolutely. C Code is not necessarily easy to understand. Not at all.
But the C parts itself are? In C you seldom wonder what C does, you wonder what the C code does.
If you work like OP likes, single person projects, small enough to keep almost completely in your head, I can see how C is especially charming. You never wrangle with C, only with your own code.
You do realise that C and C++ are among the languages (there is also Perl) having anual obfuscated writing contests, right?
Being self evident is certainly not something I associate with C code.
But usually the most controversial features are:
- use of exceptions
- use of RTTI and dynamic_cast
- use of stdlib types which allocate memory under the hood
- ...or even use of any stdlib types
- has boost or similar complex dependencies
- use of 'modern C++' features that explode compile times (like the range stuff)
...etc etc... when you look at C++ libraries written in the game development or embedded hemisphere, those usually use an entirely different C++ subset than the more academic "modern C++" crowd.
* core, whose only dependencies are six symbols (memcpy, memcmp, memset, strlen, rust_begin_panic, and rust_eh_personality)
* alloc, which layers on top of core, and adds the ability to allocate memory
* std, which includes all of the operating system specific things
But most importantly, you can declare that your code doesn't require std and alloc with an annotation, and there's a way to tag these crates in the package manager as well. Additionally, cargo has a "features" feature, so you can say "please give me the no_std version of the library" and get what fits within your needs.
My team at work is working purely in the 'core' subset, and we're able to easily find and evaluate open source projects that will work for us, because of this.
Oh, I should also mention: while Rust doesn't have exceptions, there's also a way to compile your program so that panics abort instead of unwind the stack, similar to -fno-exceptions being passed when compiling C++ code. There is also a setting you can include so that, if, for some reason, you require the unwinding semantics, if someone tries to build a project with abort semantics and your program, it will not allow it and let you know why. I don't think I've ever seen this annotation on a package in the wild, because since they're not used in the regular course of programming in Rust, virtually every open source library does not use them for important semantics and so therefore works with either setting.