back
398 comments
It's like reading "A Discipline of Programming", by Dijkstra. That morality play approach was needed back then, because nobody knew how to think about this stuff.

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...

Maybe it's my learning limitations, but I find it hard to follow explanations like these. I had similar feelings about encapsulation explanations: it would say I can hide information without going into much detail. Why, from whom? How is it hiding if I can _see it on my screen_.

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.

That's not explaining ownership, that motivating it. Which is fine. The thing that's hard to explain and learn is how to read function signatures involving <'a, 'b>(...) -> &'a [&'b str] or whatever. And how to understand and fix the compiler errors in code calling such a function.
Summarizing a set of concepts in a way that feels correct and complete to someone who understands them, is a much easier task than explaining them to someone who doesn't. If we put this in front of someone who's only worked with call-by-sharing languages, do you think they'll get it right away? I'm skeptical.
And, after someone who doesn't know rust reads this neat and nice summary, they would still know nothing about rust. (Except "this language's compiler must have some black magic in it.")
I think the most important lesson is this:

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.

This explanation doesn't expose anything meaningful to my mind, as it doesn't define ownership and borrowing, both words being apparently rooted in an analogy with financial asset management.

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?

That really doesn't explain the model because you have completely left out the distinction between exclusive/shared (or mutable/immutable) borrows. Rust made a large number of choices with respect to how it permits such borrows and those do not follow from this brief outline nor from intuition or common sense. For example, the no aliasing rule is motivated not by intuition or common sense but from a desire to optimize functions.

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 usually teach it by translating it to our physical world by way of an object like a book, which I like to think is intuitive.

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.

Seems incomplete. E.g. what happens if a borrower goes away?
>the real owner has to be a reference-counted cell.

And what is that? Its easy to fall in the trap of making explanations that is very good (if you already understand).

I still haven't gotten into rust yet, mostly due to time and demand, but, I have been doing a lot of C++ in the past few years.

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.

I think the Brown University’s modifications to the rust book do an excellent job of explaining the borrow checker.
It took me a few tries to get comfortable with Rust—its ownership model, lifetimes, and pervasive use of enums and pattern matching were daunting at first. In my initial attempt, I felt overwhelmed very early on. The second time, I was too dogmatic, reading the book line by line from the very first chapter, and eventually lost patience. By then, however, I had come to understand that Rust would help me learn programming and software design on a deeper level. On my third try, I finally found success; I began rewriting my small programs and scripts using the rudimentary understanding I had gained from my previous encounters. I filled in the gaps as needed—learning idiomatic error handling, using types to express data, and harnessing pattern matching, among other techniques.

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.

Rust has a few big hurdles for new users:

- 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.

"Safe" Rust is generally a simple language compared to C++. The borrow checker rules are clean and consistent. However, writing in it isn't as simple or intuitive as what we've seen in decades of popular systems languages. If your data structures have clear dependencies—like an acyclic graph then there's no problem. But writing performant self-referential data structures, for example, is far from easy compared to C++, C, Zig, etc.

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.

As a systems programmer I found Rust relatively easy to learn, and wonder if the problem is non-systems programmers trying to learn their first systems language and having it explicitly tell them "no, that's dangerous. no, that doesn't make sense". If you ask a front end developer to suddenly start writing C they are going to create memory leaks, create undefined behavior, create pointers to garbage, run off the end of an array, etc. But they might "feel" like they are doing great because there program compiles and sort of runs.

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).

> Stop resisting. That’s the most important lesson

> 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

My problem with rust is not the learning curve, but the absolute ugliness of the syntax. It's like Perl and C++ template metaprogramming had a child. I just can't stand it.

Python is my favourite, C is elegance in simplicity and Go is tolerable.

Rust is wonderful but humbling!

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.

One approach that I don't see often enough is to focus on learning a subset of the language first. For example, in my own book on Rust, I skip teaching lifetimes. It's not necessary to write functions with lifetimes to build quite a few fully functioning programs. The same with macros (although the fact that their signatures are opaque doesn't make it easy for beginners). On the other hand, I disagree with the advice to rely on copy() or clone() - it's better to learn about borrowing from the beginning since it's such a fundamental part of the language.
>For instance, why do you have to call to_string() on a thing that’s already a string?

It's so hard for me to take Rust seriously when I have to find out answers to unintuitive question like this

A learning curve measures time on the x axis and progress on the y axis.

A flat learning curve means you never learn anything :-\

This reads like a list of symptoms of what's wrong with the ergonomics of Rust. This is not to bash Rust. It has its uses. But you need to balance what you are sacrificing for what you are getting.
Ownership and lifetimes are arguably not about Rust, but about how we construct programs today. Big, interdependent, object graphs are not a good way of constructing programs in Rust or C++.
The only way I think I’ll learn rust is if there’s a surge of job opportunities paying 300 K and up which require it.

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

I'm not sure there are many cases where I would choose rust. I'm open to it. I just think in any given situation there would most likely be a better option.

Perhaps it will become prevalent enough that it will make sense in the future.

> Use String and clone() and unwrap generously; you can always refactor later

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 thought it was quite manageable at beginner level…though I haven’t dived into async which I gather is a whole different level of pain
These replies mainly revealed to me the general response people have on being corrected. Its very easy to become a stubborn programmer after being in the industry for so long.

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?

Feels like a cult manual. "Do all things our way! Don't question anything!"
I seem to have eased into a rust programming style that dodges these, perhaps at the cost of some optimizations. Using the first example, for example (The article suggests this): Don't return an `&str`; use those for transient things only like function parameters; not for struct fields or returned types.

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!

Is there a concise document that explains major decisions behind Rust language design for those who know C++? Not a newbie tutorial, just straight to the point: why in-place mutability instead of other options, why encourage stack allocation, what problems with C++ does it solve and at what cost, etc.
The whole learning curve seems overblown to me. You don’t need to grok every part of the language to start using it and being productive.
For me the most important thing about Rust is to understand that it's a langue with value semantics. Which makes it completely different than every mainstream language you encountered so far.

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.

"You will have a much better time if you re-read your code to fix stupid typos before pressing “compile.”"

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.

Write a CHIP8 emulator!

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.

[flagged]
Good article, thoughtful and well-written and (idioms aside) much of it applicable to learning other languages.
Compared to other programming languages, Rust's compiler and linters go a long way to implement best practices at build time.
What is it that make that rust is said to have steep learning curve compared to other programing languages (in the same category)?
Regarding the first example, the longest() function, why couldn't the compiler figure it out itself? What is the design flaw?
> Treat the borrow checker as a co-author, not an adversary

Why would I pair-program with someone who doesn’t understand doubly-linked lists?

Surrender! to compile

Weather the ferocious storm

You will find, true bliss

Traits are yet another way of doing the CS concept of interfaces, regardless of the author's opinion.

Polymorphic dispatch on a set of known operations, that compose a specific type.

Sorry, but it's easier to learn basic C++/C, and let beginner developers get lessons from a basic linter or clang-tidy for about the same result, for a fraction of the developer cost.

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.

> …. For instance, “a trait is a bit like an interface” is wrong, …

Wait. What's wrong with this statement?

Does anyone still have trouble learning Rust? I thought that was kind of a 2015 thing