Rust seems to be overally punishing
Rust seems to be overally punishing
Rust has very strict checks, and we -want- those. That's why we're using Rust in the first place.
I -could- use a more convenient / lenient language like ruby or golang but I generally wouldn't prefer it unless the use case made sense. You end up "paying" in different ways later down the line as your codebase grows in complexity.
In my experience, the rust compiler is extremely helpful and most issues I had went away after reading the rust book. [0]
The difference between Rust and Go, on the other hand, is mostly pragmatic: Rust is "strict" about design and implementation details Go's runtime handles for the programmer; there's nothing for Go to be "strict" about.
Rust is more rigorous than Go, but to a much smaller degree than Go is over Ruby.
Regarding memory management, sure, but Rust's strictness also handles things that Go doesn't. Two examples are error handling --- Result<T,E> in Rust ensures that you check for errors before using a result --- and thread safety --- the borrow checker prevents many synchronization errors that Go doesn't catch.
Rust gives a hard shove where Go gives something closer to a nudge, but in practice, Go's nudge (the requirement to do something with an error or else bear the shame of _'ing it) gets me 99.999% of the way to Result.
Rust has an `Error` trait too, and you can always just use `Box<dyn Error>` as your error type if you're fine with using a heap allocation to avoid caring about the concrete type (which is what's happening in Go when you use the error interface as your return type anyhow). Rust just makes the more efficient (in terms of runtime performance, not necessarily usability) the default and the more expensive option opt-in. This is a common pattern in a lot of the language design choices: performant by default, with the option to opt into a less performant variant to make things easier if you're okay with the cost. I think a lot of newcomers to the language (not saying you are one; I have no idea what your experience level is) don't realize this because the Rust community tends tends to _really_ like doing things the "proper" way. This isn't a problem per se, but I think there would be immense value in having more resources that made all of the options on the performance versus usability spectrum more clear.
One could argue that outside of safety guarantees that Rust provides it also happens to be a good language that people would like to use for those reasons (I really like the tooling, ecosystem and standard library when it comes to Rust, not so much the language itself).
In retrospective, I make the life harder:
* Thinking like in the other langs I have learned (with exception of my first 2) is enough to just read fast some tutorial and start coding... I relent and pick a book to learn it right.
* Start worrying so much about performance, when never before I bothered much (what! this can ALLOCATE!). Now I clone like madman!
* Try to fit the OO mentality to Rust. Even in F# I don't fully commit to POCO objects and functions as I do in Rust now. Also, the lacks of polymorphism is what hurt me sometimes (ie: is harder in rust)
But now? Rust feel ABSURDLY productive. The thing I notice is how much I refactor stuff now, (even more than in F# that also was a up in this regard).
The "punishment" parts is only in my toy interpreter and very small now. For my main project ( a ERP/ecommerce app rewrite from F#) Is great!.
With that stated, I would encourage you to understand why rust was designed this way and why the strictness of the compiler is actually a major advantage compared to languages like Python and JS where a lack of a type system actually makes the software much more prone to bugs.
Rust has many quality-of-life improvements that could be transplanted to a more relaxed GC-based language, but that won't be Rust any more. Here are some musings how such language could look like:
https://without.boats/blog/revisiting-a-smaller-rust/
(OT: why OP got downvoted so hard? It's a reasonable question to ask)
I also am disappointed that the OP got downvoted. Seems bad.
Ultimately I did get a solid explanation here. If I want a quick, fast and dirty language , Rust doesn't fit the role.
I really do love how C# gives me the ability to hack things together quickly, with still stopping me from doing things which simply won't work. Flutter takes this even a step further with the dynamic keyword( C# has dynamics too , but they aren't as robust ). I can write function calls with dynamics which is great for prototyping. Eventually bad things will happen if you use dynamics in every single call , but it helps me get started.
I definitely understand why many software developers see this as a dumbing down of software engineering, which I would attribute the downvotes to.
I would never write a large production app in Python though (having done it a couple of times). The flexibility becomes unwieldy at that scale and you have to have tests that exercise every corner or you'll have problems hours or days into running.
With Rust do you need less Unit testing ?
I find myself upvoting tons of comments in Rust threads where those comments are just asking questions, but are, for whatever reasons, downvoted. Happens on the Rust subreddit too. I don't know wtf it's about.
You make the sacrifice of a "punishing" compiler, and spending more time writing code, but you gain software that's more likely to be "correct", safe, and not leak memory.
Really, as in all things computers, it depends what your set of requirements are as to what trade offs you make
Intuitively it makes a lot of sense, and it jives with my subjective feelings (especially for longer-lived projects), but I'm not sure if there have been any studies to back up that claim.
https://danluu.com/empirical-pl/ is a good place to get started on this topic.
Moving from one of those languages to a modern statically typed language can come with a feeling that your programs tend to run successfully as soon as they compile --- certainly they do more often than they would in Ruby. Because the compiler is doing some of that testing work for you. It's a good feeling.
But it hasn't been my experience that the extra rigor in Rust's model produces the same boost from Java or Go; in fact, my first-run experience with Rust is generally less reliable than Go; there are a few more things that can go wrong with a Rust program and slip by the compiler.
Which is what you'd expect; Rust isn't garbage collected, so the programmer has to do the garbage collection, and every program is responsible in some sense for designing and implementing that part of its own runtime. There are things you can get wrong there that GC'd languages don't make you bother trying to get right. And, of course, if you take the time to get your per-program runtime really right, there's a performance gain to be had from doing so. It's all tradeoffs.
This seems like a really odd thing to say in at least two dimensions. Firstly, I would say that "the programmer has to do the garbage collection" isn't an accurate way to describe Rust. Secondly, and particularly in comparison with Go, Rust's RAII applies more universally than Go's garbage collection. How many times have you seen a missing defer or a defer inside of a loop? I've seen both quite a bit. But those bugs basically don't happen in Rust precisely because the programmer almost never needs to deal with destruction directly. It's done automatically by the compiler.
As to the broader point, I would very strongly oppose the notion that Rust is less reliable than Go. While I don't think I would argue that Rust gives the same boost over Go that, say, Java or Go give over Ruby (that's a tough argument to make anyway, even if I agreed with it), I would say there is a meaningful boost. But that's a tough argument to make too. Aside from the obvious bits (nil errors and forgetting to check errors), for me, it basically comes to my belief that Go punishes its practitioners for abstraction. I wrote a bit more about that here: https://users.rust-lang.org/t/what-made-you-choose-rust-over...
Another point, which to your credit you didn't make but which is nonetheless true, is that I am not a good Rust programmer!
I don't think Rust is less reliable than Go in the large. I do think there are more things you have to get right in a Rust program than in a Go program, so it's "less reliable" in that weird twilight state when you're first bringing up your program.
I do agree that Go punishes practitioners for abstraction. If you're, like, you, that's a very bad thing. If you're working with a team of people for whom the project is a means to an end and not a brilliant-cut gemstone, putting the brakes on abstraction can be a good thing, which I think a lot of Rust programmers will quickly learn after the nth time they've had to do an edit-compile cycle just to `let () = something` to figure out a type.
The point about having too much power is taken, and I don't have experience with that in a team context. And yeah, I am not exploring the Rust downsides as much here, and there are definitely others.
Although you could characterize it as "Rust makes the programmer think about and describe the garbage collection."
Really, I think most argumentation comes down to “the type system of the programming language I want to use anyway is the right one, and everything else is wrong”.
If I'm slinging together an internal CRUD app, the extra time thinking about types probably will slow me down because the major risks aren't really type problems.
But if for example I'm working on something in a game engine, being able to use the language and compiler to enforce assumptions/invariants at compile time is really useful.
The much bigger problems imho are not really tractable. e.g. consider something that does:
struct server {
std::vector<requests> m_requests_to_process;
boo m_running{true};
void on_web_request(request req) {
m_requests_to_process.push_back(req);
}
void process_pending_requests() {
while(m_running) {
for(auto req : m_requests_to_process) {
do_stuff(req);
}
// oops, forgot to clear
}
}
};
there's no "leak" in the OS sense as everything will be reclaimed upon server::~server... but your memory usage will be strictly increasing. Can Rust detect those cases ? As in my experience this is the most common leak pattern (of course not in a simple example like this, but when your "server" architecture starts being split across multiple threads and source files...This particular case might be because you would probably consume the vector in the loop, which would free, but you could absolutely write the exact same code in Rust with the same end result.
However, it may help in this specific case.
In your example you are iterating over `m_requests_to_process`. As you are using `auto` instead of `auto &` it automatically clones the elements of a vector. In Rust, it's not possible to clone an element by accident. Objects are moved by default (think `std::move`) and if you want to clone instead you need explicitly use `.clone()`.
If `do_stuff` function takes ownership of a request (as in, if it takes `Request` parameter and not `&mut Request` parameter) than the problem will be pointed by Rust, and so the compiler wouldn't allow you write code like this forcing programmer to write code that removes elements from the list to take ownership of them. For example:
for req in mem::take(&mut self.requests_to_process) {
do_stuff(req);
}
`mem::take` (https://doc.rust-lang.org/std/mem/fn.take.html) replaces an object behind a mutable reference with a default value for a given type (empty vector in case of vectors) and gives you an ownership over vector. struct request {
int id;
double parameter_value;
};
surely moving does not gain anything - the original vector object still keeps the memory allocated, right ? sure, if you have complex requests with substrings, etc, but in that case I'd have `const auto&`-ed :)(from my experience going full-throttle on movability when C++11 came out, I'd say that this was a mistake overall, much better to keep things as const& most of the time if you can. I've not yet reached a state where I consider the need for ownership transfer a code smell... but not very far :-))
You could also do something with the drain method (which they posted originally and changed to the current implementation, not 100% sure why) and that would keep the memory around, yes, but then you'd be with only the high water mark of the number of requests, because it would be re-used.
`Vec::drain`: https://doc.rust-lang.org/std/vec/struct.Vec.html#method.dra...
Keep working at it. After a while you start to realize the patterns that work. Now, I can prototype in Rust pretty fast.
There are also a lot of nice features in Rust other than the pure absolute runtime efficiency.
> There are also a lot of nice features in Rust other than the pure absolute runtime efficiency.
Certainly! I reread my earlier post and see how it could have been more qualified; what I meant to say was if the burden of the borrow-checker is so high that the user needs to use the ~Rc~ by default /I/ would find it hard to justify Rust for /that/ type of programming over other languages with similar features.
> it seems to me that it would defeat the point of using Rust at all and go with a strongly-typed GC language.
This seems to be a regular comment when talking about Rust (among other "incremental improvement" approaches) and I don't really get it: for 95% of the code things are simple enough that you don't have to think too much about it, the other 5% can either rely on one of the multiple-ownership safe abstractions or use unsafe. To me this framing sounds like "why do I need seat belts, if I fall of a cliff it won't do any good!"
> To me this framing sounds like "why do I need seat belts, if I fall of a cliff it won't do any good!"
I’m sorry that that’s how you read my comment — the languages I tend to use are much higher-level than Rust, like Haskell and Lisp, and I constantly found myself having to restructure my code to deal with memory-management. In particular, I found closures and futures difficult to work with — compared to a higher-level GC language. This experience may or may not be typical; I have few data-points to draw on after all!
Though I have found that the borrow checker often ends up pushing architectural changes on me as I'm fixing things.
This might be because I'm just newer with Rust, but I personally would find resolving 30 borrow-checker issues at once to be much more painful that resolving them as I go.
In C having to free allocated memory manually dictates how data structures are built and how your program flows. Often you have to keep track of extra state in order to free your memory at the right time and if you don't have this in mind from the get-go you're going to write code that leaks a lot of memory or doesn't work. The same is true for rust, but instead of the code leaking or not working it doesn't compile. If you turn those checks off with unsafe you're essentially stuck with an improved C that's just as difficult to prototype in.
Systems programming languages are simply not designed for fast prototyping, instead focusing on fast and in rust's case correct code. A GC, dynamic typing, high level library, etc. all help you prototype faster but you're not going to be able to use them to write an OS or a web browser.
There are also some immature crates for tracing garbage collection in Rust, like rust-gc, which typically has higher throughput than reference counting.
I too don't like rust evangelism around here. But any developer should know at least one system language, understand algorithm complexity, understand basics about memory management and caches, etc.. And probably it is easier to learn rust than C++ to those with web background.
But as an aside, if you're just trying to prototype quickly, some useful "shortcuts" can be:
- Using `Box<dyn std::error::Error>` for functions which need to handle a result
- Using `unwrap` or `expect` to easily access Option internal values
- Using `unimplemented!()` if I'm trying to mock an API design. This one is nice because it will let you build out the signature including specifying a return type without having to build out the function logic.
Obviously these are shortcuts and not production suitable, but can be good to just get some code on the page that compiles
There's nothing preventing you from using multiple languages on your app. Say, Rust for foundations and Lua/Python/Scheme/Tcl/Whatever for glue.