With Rust I've found that even though sometimes it feels like it is harder to write code with, I am finding it far more likely that it is correct when the compiler is happy with it, and I find myself worrying much less about move semantics/correctness/pointers and all that stuff like I was with C++, it has allowed me to move faster and not spend as much mental time on trying to make sure/understand if what I am doing is ACTUALLY safe.
I am using C++ as an example, because the other language I use regularly is Python and there are still pieces of Python code that make me scratch my head with "how does THAT work?!"
it's pretty interesting though - surely you would have flagged the equivalent C++ code to what is discussed in https://github.com/rust-lang/rust/issues/93740, if my understanding is correct - akin to std::unique_ptr<pthread_mutex_t>, as obviously wrong ? It's a pattern that was apparently commonplace in rust until now yet I would definitely not let my first year comp. sci. interns get away with something like this during code review.
In other languages I like, I reach a point where I’m generally confident my code will compile before I ask it to. In Rust, for all but the most trivial logic, the compiler will usually have several things to say. However, I usually feel like the compiler is guiding me towards a simpler/more idiomatic/better way of doing things, and the compiler is more strict simply because Rust is so well-equipped for static compile-time analysis.
So fret not, the borrow checker and all the other checks that happen when writing Rust exist precisely because these things are so painful and complex to reason about as a human. The compiler is your colleague, not your boss!
Unfortunatelly logic errors are not caught - this would require some sophisticated AI in compiler. But other problems should be caught.
Of course Rust will not prevent you from placing backdoors and other more sophisticated vulnerabilities in code. Compiler is great, but you still have to think.
Although obviously no compiler of a Turing-complete language is going to eliminate all logic errors, the user of a language like Rust or Haskell may use the type system to prevent certain classes of logical errors (not just problems with the shape of data, or incorrect memory handling). The way you do it is with Abstract Data Types. One example of such a type in Rust is &str. If you don't use unsafe code, it should preserve the invariant that the slice holds valid UTF-8 data. Containing invalid UTF-8 data would be a logical error, not a memory error or data shape error. Similar things may be achieved in C++ and Java with the use of access-modifiers (public vs private class fields and methods). The idea is well-explained in the famous Parse, don't validate[1] article.
The flipside is that too much of it and code becomes so complicated, it's very hard to work with --- you're falling into a Turing tarpit[2]. It becomes easier to just write simple code without bugs, without using all that type system wizardry. But a judicious use of this pattern, where it's appropriate, may be very beneficial.
[1]: <https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-va...>
If you haven't tried again in a while, you should give it another attempt -- I found it much easier after the 2018 edition was finalized, in part because of significant improvements to the borrow checker[1]. In terms of programming ergonomics, the pre-2018 and post-2018 versions of the language feel very different, even if they happen to share most of the same syntax.
[1]: https://blog.rust-lang.org/2018/12/06/Rust-1.31-and-rust-201...
* &mut is an "infectious leaky abstraction". It places restrictions on the caller, specifically that nobody can have a reference to that data. This disqualifies many useful patterns such as backreferences, observers, dependency references, graphs, delegates, and certain kinds of RAII.
* Rust tends to lean very heavily on the type system to surface as much detail as possible into e.g. function signatures, but can conflict when the signature is set in stone, such as when implementing a trait or exposing a public function. It also does this with non-type-system denizens, such as async/await.
* A reluctance to fall back to Rc and RefCell. Programs often have inherent shared mutability, and the alternatives are often more complex.
These restrictions make the borrow checker incompatible with code we'd naturally write in any other language. Luckily, with enough practice, it can "click" and one can get used to the architectures that are compatible with its restrictions.
The tradeoff isn't ideal for some use cases. For others, it can be a great fit.
A lot of library devs seem to have read "strong type system" and taken it as a challenge, meaning that half of their code is actually declarative to the compiler rather than readable in the source file. As a native Java dev this is very annoying.
For 90% of people I think the main hurdle is realising this.
Doing it the "basic" way is sooooooo much easier, which is important while learning. Nine times out of ten it will be fast/efficient enough anyway.
After you've got a handle on it with a few non-trivial programs under your belt, then start tightening up once you have the necessary context and familiarity.
While the Rust syntax around lifetimes can be confusing, The Rust Book does a decent guide describing what lifetimes are. Reading up on Modern C++ practices and language constructs actually helps with Rust too.
The rustdoc book doesn't have a lot of examples of the full markdown support for documenting your code unfortunately. Browsing through std helps.
Cargo rocks, and it's easy to spin up a new project. One thing I did was create a few new ones just for concepts that were difficult to me at first.
It's a pretty quick read and gives you a good handle on Rust fairly quickly.
You can read the reference book, and even a few other ones, but still not being able to have a sufficient/solid grasp of the language. I couldn't find good material past the beginner stage (I consider Rust for Rustaceans advanced).
My personal advice is to find some project you'd like to implement, and practice a lot, or contribute to small projects you like, without worrying about best practices.
There have been definitely many moments where I wanted to close the Rust chapter, and that's why I think it's more important than other languages to keep the motivation high, which I think is best achieved by working on projects.
Rustlings is nice but... not stimulating.
Its a difficult language to learn full-stop. And its a language that can be used to produce valuable software, but probably should not be the first choice for many applications at most organizations.
It takes time and effort to adjust... a lot of time and effort :)
In my personal experience, there are two major hurdles.
Access model is the first (I believe it's what is commonly referred to a "borrow checking"). It just happened one day that I was understanding the access model, and not screaming in terror anymore :) The same may happen to you, so don't feel incapable in the meanwhile.
Lifetimes is the second hurdle, but even before fully understanding them, you'll be able to work on complex programs.
Edit : thanks, all! very cool
https://doc.rust-lang.org/std/index.html
e.g https://doc.rust-lang.org/std/keyword.as.html explains the 'as' keyword
https://doc.rust-lang.org/std/primitive.i128.html documents the 128-bit signed integer type
Or, maybe you are thinking purely a language document like:
In addition https://docs.rs contains the documentation for all publicly available Rust code.
The main tool used to generate these docs is very good and easy to use. As a result documentation is usually pretty good.
- read as much learning material as possible
- write some non-trivial learning code, possibly a project you already know
- get a little frustrated, walk away
....wait three months
- come back
- re-read what you had done earlier
- realize you actually knew quite a bit and you can now "learn from yourself"
- make progress