Most explanations of ownership in Rust are far too wordy. See [1]. The core concepts are mostly there, but hidden under all the examples.
- Each data object in Rust has exactly one owner.
- Ownership can be transferred in ways that preserve the one-owner rule.
- If you need multiple ownership, the real owner has to be a reference-counted cell.
Those cells can be cloned (duplicated.)
- If the owner goes away, so do the things it owns.
- You can borrow access to a data object using a reference.
- There's a big distinction between owning and referencing.
- References can be passed around and stored, but cannot outlive the object.
(That would be a "dangling pointer" error).
- This is strictly enforced at compile time by the borrow checker.
That explains the model. Once that's understood, all the details can be tied back to those rules.[1] https://doc.rust-lang.org/book/ch04-01-what-is-ownership.htm...
Similarly here, I can't understand for example _who_ is the owner. Is it a stack frame? Why would a stack frame want to move ownership to its callee, when by the nature of LIFO the callee stack will always be destroyed first, so there is no danger in hanging to it until callee returns. Is it for optimization, so that we can get rid of the object sooner? Could owner be something else than a stack frame? Why can mutable reference be only handed out once? If I'm only using a single thread, one function is guaranteed to finish before the other starts, so what is the harm in handing mutable references to both? Just slap my hands when I'm actually using multiple threads.
Of course, there are reasons for all of these things and they probably are not even that hard to understand. Somehow, every time I want to get into Rust I start chasing these things and give up a bit later.
Ownership is easy, borrowing is easy, what makes the language super hard to learn is that functions must have signatures and uses that together prove that references don't outlive the object.
Also: it's better not store referenced object in a type unless it's really really needed as it makes the proof much much more complex.
I'm not acquainted with Rust, so I don't really know, but I wonder if the wording plays a role in the difficulty of concept acquisition here. Analogies are often double edged tools.
Maybe sticking to a more straight memory related vocabulary as an alternative presentation perspective might help?
The most complicated aspect of the borrows comes about from the elision rules which will silently do the wrong thing and will work fantastically until they don't at which point the compiler error is pointing at a function complaining about a lifetime parameter of a parameter with the trait method implying that the parameter has to live too long but the real problem was a lifetime in the underlying struct or a previous broken lifetime bound. Those elision rules are again not-intuitive and don't fall out of your explanation axiomatically. They were decisions that were made to attempt to simplify the life of programmers.
I have a book. I own it. I can read it, and write into the margin. Tear the pages off if I want. I can destroy it when I am done with it. It is mine.
I can lend this book in read only to you and many others at the same time. No modifications possible. Nobody can write to it, not even me. But we can all read it. And borrower can lend it recursively in read only to anybody else.
Or I can lend this book exclusively to you in read/write. Nobody but you can write on it. Nobody can read it; not even me; while you borrow it. You could shred the pages, but you cannot destroy the book. You can share it exclusively in read/write to anybody else recursively. When they are done, when you are done, it is back in my hands.
I can give you this book. In this case it is yours to do as you please and you can destroy it.
If you think low level enough, even the shared reference analogy describes what happens in a computer. Nothing is truly parallel when accessing a shared resource. We need to take turns reading the pages. The hardware does this quickly by means of cached copies. And if you don't want people tearing off pages, give then a read only book except for the margins.
And what is that? Its easy to fall in the trap of making explanations that is very good (if you already understand).
Coming from that background these rules sound fantastic, theres been a lot of work put into c++ the past few years to try and make these things easier to enforce but it's still difficult to do right even with smart pointers.
After all this ordeal, I can confidently say that learning Rust was one of the best decisions I’ve made in my programming career. Declaring types, structs, and enums beforehand, then writing functions to work with immutable data and pattern matching, has become the approach I apply even when coding in other languages.
- it's very different from other languages. That's intentional but also an obstacle.
- it's a very complex language with a very terse syntax that looks like people are typing with their elbows and are hitting random keys. A single character can completely change the meaning of a thing. And it doesn't help that a lot of this syntax deeply nested.
- a lot of its features are hard to understand without deeper understanding of the theory behind them. This adds to the complexity. The type system and the borrowing mechanism are good examples. Unless you are a type system nerd a lot of that is just gobblygook to the average Python or Javascript user. This also makes it a very inappropriate language for people that don't have a master degree in computer science. Which these days is most programmers.
- it has widely used macros that obfuscate a lot of things that further adds to the complexity. If you don't know the macro definitions, it just becomes harder to understand what is going on. All languages with macros suffer from this to some degree.
I think LLMs can help a lot here these days. When I last tried to wrap my head around Rust that wasn't an option yet. I might have another go at it at some time. But it's not a priority for me currently. But llms have definitely lowered the barrier for me to try new stuff. I definitely see the value of a language like users. But it doesn't really solve a problem I have with the languages I do use (kotlin, python, typescript, etc.). I've used most popular languages at some point in my life. Rust is unique in how difficult it is to learn.
On the opposite, "Unsafe" Rust is not simple at all, but without it, we can't write many programs. It's comparable to C, maybe even worse in some ways. It's easy to break rules (aliasing for exmaple). Raw pointer manipulation is less ergonomic than in C, C++, Zig, or Go. But raw pointers are one of the most important concepts in CS. This part is very important for learning; we can't just close our eyes to it.
And I'm not even talking about Rust's open problems, such as: thread_local (still questionable), custom allocators (still nightly), Polonius (nightly, hope it succeeds), panic handling (not acceptable in kernel-level code), and "pin", which seems like a workaround (hack) for async and self-referential issues caused by a lack of proper language design early on — many learners struggle with it.
Rust is a good language, no doubt. But it feels like a temporary step. The learning curve heavily depends on the kind of task you're trying to solve. Some things are super easy and straightforward, while others are very hard, and the eventual solutions are not as simple, intuitive or understandable compared to, for example, C++, C, Zig, etc.
Languages like Mojo, Carbon (I hope it succeeds), and maybe Zig (not sure yet) are learning from Rust and other languages. One of them might become the next major general-purpose systems language for the coming decades with a much more pleasant learning curve.
If you have already gotten to the journeyman or mastery experience level with C or C++ Rust is going to be easy to learn (it was for me). The concepts are simply being made explicit rather than implicit (ownership, lifetimes, traits instead of vtables, etc).
> Accept that learning Rust requires...
> Leave your hubris at home
> Declare defeat
> Resistance is futile. The longer you refuse to learn, the longer you will suffer
> Forget what you think you knew...
Now it finally clicked to me that Orwell's telescreen OS was written in Rust
Python is my favourite, C is elegance in simplicity and Go is tolerable.
It has a built in coach: the borrow checker!
Borrow checker wouldn't get off my damn case - errors after errors - so I gave in. I allowed it to teach me - compile error by compile error - the proper way to do a threadsafe shared-memory ringbuffer. I was convinced I knew. I didn't. C and C++ lack ownership semantics so their compilers can't coach you.
Everyone should learn Rust. You never know what you'll discover about yourself.
It's so hard for me to take Rust seriously when I have to find out answers to unintuitive question like this
A flat learning curve means you never learn anything :-\
The potential is definitely there, it looks like it might compete with C++ in the quant .
But we already have ocaml . From Jane Street, at least for me if you’re going to tell me, it’s time to learn an extremely difficult programming language, I need to see the money.
So far my highest paid programming job was in Python
Perhaps it will become prevalent enough that it will make sense in the future.
At that point you might as well be writing Java or Go or whatever though. GC runtimes tend actually to be significantly faster for this kind of code, since they can avoid all those copies by sharing the underlying resource. By the same logic, you can always refactor the performance-critical stuff via your FFI of choice.
I'd advise these people to personally figure out why they're so against compiler suggestions. Do you want to do things differently? What part stops you from doing that?
I'm starting to wonder what I'm missing out by doing this. Not addressed in the article: Any tips for using the more abstract features, like Cow etc? I hit a problem with this today, where a lib used Cow<&str> instead of String, and the lifetime errors bubbled up into my code.
edit: I found this amusing about the article: They demo `String` as a param, and `&str` as a return type for triggering errors; you can dodge these errors simply by doing the opposite!
Variable in rust is not a label you can pass around and reuse freely. It's a fixed size physical memory that values can be moved into or moved out of. Once you understand that everything makes sense. The move semantics, cloning, borrowing, Sized, impl. Every language design element of rust is a direct consequence of that. It's the values that get created, destroyed and moved around and variables are actual first-class places to keep them with their own identity separate from values that occupy them. It's hard to notice this because Rust does a lot to pretend it's a "normal" language to draw people in. But for anyone with experience in programming that attempts to learn Rust I think this realization could make the process at least few times easier.
It's hard to shift to this new paradigm and embrace it, so in the meantime feel use a lot of Rc<> and cloning if you just need to bang out some programs like you would in any other mainstream language.
This is a strange one - I thought the rust compiler had famously helpful error messages, so why would I want to pore over my code looking for stupid typos when I can let the compiler find them for me? I am guaranteed to make stupid typos and want the computer to help me fix them.
Bonus: do it with no heap allocation. This actually makes it easier because you basically don’t deal with lifetimes. You just have a state object that you pass to your input system, then your guest cpu system, then your renderer, and repeat.
And I mean… look just how incredibly well a match expression works for opcode handling: https://github.com/ablakey/chip8/blob/15ce094a1d9de314862abb...
My second (and final) rust project was a gameboy emulator that basically worked the same way.
But one of the best things about learning by writing an emulator is that there’s enough repetition you begin looking for abstractions and learn about macros and such, all out of self discovery and necessity.
Why would I pair-program with someone who doesn’t understand doubly-linked lists?
Weather the ferocious storm
You will find, true bliss
Polymorphic dispatch on a set of known operations, that compose a specific type.
I want rust to be adopted and I believe companies should force it, but you will not get adoption from young developers and even less from senior C++ developers.
Not to mention rewriting existing C++ code in rust, which cost would be astronomical, although I do believe companies should invest in rewriting things in rust because it's the right thing to do.
Wait. What's wrong with this statement?