Flattening Rust’s learning curve

159 birdculture 119 5/13/2025, 10:25:29 PM corrode.dev ↗

Comments (119)

Animats · 5h ago
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...

raincole · 3h ago
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.")
ameliaquining · 5h ago
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.
bloppe · 4h ago
For me it really clicked when I realized ownership / lifetimes / references are just words used to talk about when things get dropped. Maybe because I have a background in C so I'm used to manual memory management. Rust basically just calls 'free' for you the moment something goes out of scope.

All the jargon definitely distracted me from grasping that simple core concept.

josephg · 4h ago
Almost all of it.

Rust also has the “single mutable reference” rule. If you have a mutable reference to a variable, you can be sure nobody else has one at the same time. (And the value itself won’t be mutated).

Mechanically, every variable can be in one of 3 modes:

1. Directly editable (x = 5)

2. Have a single mutable reference (let y = &mut x)

3. Have an arbitrary number of immutable references (let y = &x; let z = &x).

The compiler can always tell which mode any particular variable is in, so it can prove you aren’t violating this constraint.

If you think in terms of C, the “single mutable reference” rule is rust’s way to make sure it can slap noalias on every variable in your program.

This is something that would be great to see in rust IDEs. Wherever my cursor is, it’d be nice to color code all variables in scope based on what mode they’re in at that point in time.

mikepurvis · 2h ago
"Rust basically just calls 'free' for you the moment something goes out of scope."

C++ does that too with RAII. Go ahead and use whatever STL containers you like, emplace objects onto them, and everything will be safely single-owned with you never having to manually new or delete any of it.

The difference is that C++'s guarantees in this regard derive from a) a bunch of implementation magic that exists to hide the fact that those supposedly stack-allocated containers are in fact allocating heap objects behind your back, and b) you cooperating with the restrictions given in the API docs, agreeing not to hold pointers to the member objects or do weird things with casting. You can use scoped_ptr/unique_ptr but the whole time you'll be painfully aware of how it's been bolted onto the language later and whenever you want you can call get() on it for the "raw" underlying pointer and use it to shoot yourself in the foot.

Rust formalizes this protection and puts it into the compiler so that you're prevented from doing it "wrong".

Animats · 5h ago
Right. If you come to Rust from C++ and can write good C++ code, you see this as "oh, that's how to think about ownership". Because you have to have a mental model of ownership to get C/C++ code to work.

But if you come from Javascript or Python or Go, where all this is automated, it's very strange.

math_dandy · 4h ago
The list in the above comment isn’t a summary — it’s a precise definition. It can and must be carefully explained with lots of examples, contrasts with other languages, etc., but the precise definition itself must figure prominently, and examples and intuition should relate back to it transparently.
andrewflnr · 40m ago
Practically, I think it suggests that learning the borrow checker should start with learning how memory works, rather than any concepts specific to Rust.
psychoslave · 37m ago
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?

Renaud · 12m ago
I find it strange that you relate borrowing and ownership to financial asset management.

From that angle, it indeed doesn’t seem to make sense.

I think, but might be completely wrong, that viewing these actions from their usual meaning is more helpful: you own a toy, it’s yours to do as tou please. You borrow a toy, it’s not yours, you can’t do whatever you want with it, so you can’t hold on to it if the owner doesn’t allow it, and you can’t modify it for the same reasons.

ajross · 3h ago
The second bullet in the second section is overpromising badly. In fact there are many, many, many ways to write verifiably correct code that leaves no dangling pointers yet won't compile with rustc.

Frankly most of the complexity you're complaining about stems from attempts to specify exactly what magic the borrow checker can prove correct and which incantations it can't.

mdwhatcott · 4h ago
If a language needs an article like this, absolutely begging people to bite the bullet to learn it, maybe that's a language design smell.

Disclaimer: I haven't taken the time to learn Rust so maybe don't take this too seriously..

remram · 2h ago
I don't know how to read your comment other than "nothing hard is worth doing". Some things have benefits and drawbacks, is the existence of drawbacks always a non-starter for you?

I'm trying to phrase this as delicately as I can but I am really puzzled.

If someone wrote an article about how playing the harp is difficult, just stick with it... would you also say that playing the harp is a terrible hobby?

zaptheimpaler · 37m ago
Learning any programming language at all feels 10x as hard to beginners, so you might as well say programming is not worth learning period in this case. Anything new has a learning curve to it.
devjab · 27m ago
I think you can have a lot of debate on the design decisions on Rust, but I don't think the need for these articles tell you a lot about the language itself. I'd argue that Python needs articles like this more so than Rust does, but for entirely different reasons. In two decades of more and more programmers who aren't coming from an engineering background, I've yet to see anyone who used a Python generator or slots. Data Classes are less rare, but mainly in the form of pydantics "version". Which doesn't exactly matter for a lot of Python code... This is a world where 4chan can serve 4 million concurrent users an apache server running a 10k line PHP file neither of which have been updated since 2015... so you can be fine doing inefficient and entirely in-memory Python code 95% (or more) of the time.

That doesn't mean you should though. Imagine how much energy is being wasted globally on bad Python code... The difference is of course that anyone can write it, and not everyone can write Rust. I'm not personally a big fan of Rust, I'd chose Zig any day of the week... but then I'd also choose C over C++, and I frankly do when I optimise Python code that falls in those last 5%. From that perspective... of someone who really has to understand how Python works under the hood and when to do what, I'd argue that Rust is a much easier langauge to learn with a lot less "design smell". I suppose Python isn't the greatest example as even those of us who love it know that it's a horrible language. But I think it has quite clearly become the language of "everyone" and even more so in the age of LLM. Since our AI friends will not write optimised Python unless you specifically tell them to use things like generators and where to use them, and since you (not you personally) won't because you've never heard about a generator before, then our AI overlords won't actually help.

mplanchard · 4h ago
I suspect an article like this says more about the author than the language.

Note I’m not being critical of the author here. I think it’s lovely to turn your passion into trying to help others learn.

cheikhcheikh · 1h ago
> maybe that's a language design smell

why

LAC-Tech · 3h ago
I have taken the time to learn rust and you're absolutely right. It's a very complex, design-by-committee language. It has brilliant tooling, and is still much less complex than it's design-by-committee competitor C++, but it will never be easy to learn.
yodsanklai · 2h ago
> It's a very complex

I find it relatively simple. Much simpler than C++ (obviously). For someone who can write C++ and has some experience wth OCaml/Haskell/F#, it's not a hard language.

namuol · 1h ago
Sure, C++ has a more complex spec, nobody can argue against that.

Complex is the wrong word. Baffling is a better word. Or counterintuitive, or cumbersome. If “easy enough for someone with experience in C++, OCaml, Haskell, and F#” were the same thing as “not hard” then I don’t think this debate would come up so frequently.

worik · 3h ago
There is a trade off. Rust gave us fast, and safe. It did not give us "easy to learn".

I think it is a very good example of why "design by committee" is good. The "Rust Committee" has done a fantastic job

Thank you

They say a camel is a horse designed by a committee (https://en.wiktionary.org/wiki/a_camel_is_a_horse_designed_b...)

Yes:

* Goes twice as far as a horse

* On half the food and a quarter the water of a horse

* Carries twice as much as a horse

Yes, I like design by committee. I have been on some very good, and some very bad committees, but there is nothing like the power of a good committee

Thank you Rust!

LAC-Tech · 2h ago
It's just a programming language, not a religion.
rat87 · 2h ago
its not design by committee its design by Pull request It doesn't have a central https://en.wikipedia.org/wiki/Benevolent_dictator_for_life like python used to so people suggest and implement features as a group, with code counting for a lot (although theoretical issues with safety/design also matter) as opposed to companies arguing for their pet features endlessly without much difference. Look at how long it takes C++ to get any new features.
rafram · 2h ago
> Look at how long it takes C++ to get any new features.

I’m not sure “it doesn’t have enough features” has ever been anyone’s complaint about C++.

rvz · 2h ago
Maybe Rust is so complex, it is even more complex for an LLM to generate correct code (one-shot) without hallucinating non-existent functions.

Would rather have that than all the issues that JavaScript or any other weakly typed and dynamically typed language.

namuol · 1h ago
There _are_ more than two programming languages, though. I feel like most of the debates about Rust devolve into the same false choice between safety and ease.

Before Rust I was hearing the same argument from Haskell or Scala developers trying to justify their language of choice.

I know Rust is here to stay, but I think it’s mostly because it has a viable ecosystem and quality developer tools. Its popularity is _in spite of_ many of its language features that trade that extra 1% of safety for 90% extra learning curve.

8s2ngy · 2h ago
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.

baalimago · 20m ago
>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

ants_everywhere · 5h ago
A learning curve measures time on the x axis and progress on the y axis.

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

alpinisme · 5h ago
You may be able to draw one that way but it completely neglects the way people use the term ordinarily “a steep learning curve” is not an easy to learn thing.

In point of fact, I think the intended chart of the idiom is effort (y axis) to reach a given degree of mastery (x axis)

ants_everywhere · 4h ago
I don't think the idiom has in mind any particular curve. I think it's just another case of a misuse becoming idiomatic without any meaning beyond the phrase taken as a unit. E.g.

- another think coming -> another thing coming

- couldn't care less -> could care less

- the proof of the pudding is in the eating -> the proof is in the pudding

It's usually not useful to try to determine the meaning of the phrases on the right because they don't have any. What does it mean for proof to be in a pudding for example?

The idiom itself is fine, it's just a black box that compares learning something hard to climbing a mountain. But learning curves are real things that are still used daily so I just thought it was funny to talk as if a flat one was desirable.

raincole · 3h ago
It should be called "learning hill" instead.

People (colloquially) use phrases like "steep learning curve" because they imagine learning curve is something you climb up, a.k.a. a hill.

LambdaComplex · 5h ago
"Flattening the derivative of Rust's learning curve" really doesn't roll off the tongue though
ants_everywhere · 4h ago
Yeah that's true. But it would be on brand for a post that emphasizes the importance of accuracy and attention to detail.
cozzyd · 2h ago
A steep line still has a flat derivative
Zambyte · 2h ago
It also could mean you don't need to learn beyond a certain point.
tacitusarc · 3h ago
This is incorrect. A learning curve measures expertise on the x axis and effort on the y axis. Hence the saying "steep learning curve".
tacitusarc · 1h ago
Calling it inaccurate was too harsh; my definition only became common usage in 1970, and the original “time vs learning” is still used in academic circles.
ergonaught · 3h ago
saretup · 56m ago
That’s interesting. I always intuitively assumed x-axis was progress and y-axis was cumulative effort.
tacitusarc · 1h ago
It is unclear how this comment was meant; in any case, it is appreciated. As stated in the link:

“The common English usage aligns with a metaphorical interpretation of the learning curve as a hill to climb.”

Followed by a graph plotting x “experience” against y “learning.”

autoexec · 4h ago
What we want is an "effort/difficulty curve" that measures how difficult something typically is over time from introduction to proficiency
doug_durham · 1h ago
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.
Havoc · 6h ago
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
echelon · 6h ago
Async and the "function color" "problem" fall away if your entire app is in an async runtime.

Almost 90% of the Rust I write these days is async. I avoid non-async / blocking libraries where possible.

I think this whole issue is overblown.

bigstrat2003 · 5h ago
"Just write everything async" is not remotely a good solution to the problem. Not everything needs to be async (in fact most things don't), and it's much harder to reason about async code. The issue is very much not overblown.
Salgat · 4h ago
Why is async code harder to reason about? I've been using it in C# and the entire point is that it lets you write callbacks in a way that appears nearly identical to synchronous code. If you dive into concurrency (which is a separate thing but can be utilized with async code, such as joining multiple futures at the same time), that parts hard whether you're doing it with async or with explicit threads.
Const-me · 2h ago
> I've been using it in C#

One reason why async-await is trivial in .NET is garbage collector. C# rewrites async functions into a state machine, typically heap allocated. Garbage collector automagically manages lifetimes of method arguments and local variables. When awaiting async functions from other async functions, the runtime does that for multiple async frames at once but it’s fine with that, just a normal object graph. Another reason, the runtime support for all that stuff is integrated into the language, standard library, and most other parts of the ecosystem.

Rust is very different. Concurrency runtime is not part of the language, the standard library defined bare minimum, essentially just the APIs. The concurrency runtime is implemented by “Tokio” external library. Rust doesn’t have a GC; instead, it has a borrow checker who insists on exactly one owner of every object at all times, makes all memory allocations explicit, and exposed all these details to programmer in the type system.

These factors make async Rust even harder to use than normal Rust.

umanwizard · 3h ago
> Why is async code harder to reason about?

I don't know about C#, but at least in Rust, one reason is that normal (non-async) functions have the property that they will run until they return, they panic, or the program terminates. I.e. once you enter a function it will run to completion unless it runs "forever" or something unusual happens. This is not the case with async functions -- the code calling the async function can just drop the future it corresponds to, causing it to disappear into the ether and never be polled again.

lucasyvas · 5h ago
It’s completely overblown. Almost every language with async has the same “problem”.

I’m not calling this the pinnacle of async design, but it’s extremely familiar and is pretty good now. I also prefer to write as much async as possible.

echelon · 4h ago
The "function color is a problem" people invented a construct that amplifies the seriousness. It's not really a big deal.
sodality2 · 5h ago
That’s not a solution to the coloring problem any more than making everything red was in 2015 (ie, all the tradeoffs mentioned in the article [0] still apply).

[0]: https://journal.stuffwithstuff.com/2015/02/01/what-color-is-...

spion · 5h ago
How are async closures / closure types, especially WRT future pinning?
mplanchard · 4h ago
Async closures landed in stable recently and have been a nice QoL improvement, although I had gotten used to working around their absence well enough previously that they haven’t been revolutionary yet from the like “enabling new architectural patterns” perspective or anything like that.

I very rarely have to care about future pinning, mostly just to call the pin macro when working with streams sometimes.

echelon · 5h ago
While I'd like to have it, it doesn't stop me from writing a great deal of production code without those niceties.

When it came time for me to undo all the async-trait library hack stuff I wrote after the feature landed in stable, I realized I wasn't really held back by not having it.

cadamsdotcom · 5h ago
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.

gerdesj · 4h ago
"Rust is wonderful but humbling!"

It's an abstraction and convenience to avoid fiddling with registers and memory and that at the lowest level.

Everyone might enjoy their computation platform of their choice in their own way. No need to require one way nor another. You might feel all fired up about a particular high level language that you think abstracts and deploys in a way you think is right. Not everyone does.

You don't need a programming language to discover yourself. If you become fixated on a particular language or paradigm then there is a good chance you have lost sight of how to deal with what needs dealing with.

You are simply stroking your tools, instead of using them properly.

cadamsdotcom · 2h ago
@gerdesj your tone was unnecessarily rude and mean. Part of your message makes a valid point but it is hampered by unnecessary insults. I hope the rest of your day improves from here.

I don’t specifically like Rust itself. And one doesn’t need a programming language to discover themselves.

My experience learning Rust has been that it imposes enough constraints to teach me important lessons about correctness. Lots of people can learn more about correctness!

I’ll concede- “everyone” was too strong; I erred on the side of overly provocative.

kupopuffs · 3h ago
Wow who pissed in your coffee? he likes rust ok?
codr7 · 3h ago
And he's telling other people they should like it as well, because he has seen the light.

My gut feeling says that there's a fair bit of Stockholm Syndrome involved in the attachments people form with Rust.

You could see similar behavioral issues with C++ back in the days, but Rust takes it to another level.

galangalalgol · 2h ago
I think most of us enamoured with rust are c++ refugees glad the pain is lessened. The tooling including the compiler errors really are great though. I like the simplicity of c, but I would still pick rust for any new project just for the crates and knowing I'll never have to debug a segfault. I like pytorch and matlab fine for prototyping. Not much use for in-between languages like go or c# but I like the ergonomics of them just fine. I don't think it is at all weird for people coming from c++ or even c to like rust and prefer it over those other languages. We have already paid the cost of admission, and it comes with real benefits.
awesome_dude · 2h ago
> You could see similar behavioural issues with C++ back in the days

I think that it's happened to some degree for almost every computer programming language for a whiles now - first was the C guys enamoured with their NOT Pascal/Fortran/ASM, then came the C++ guys, then Java, Perl, PHP, Python, Ruby, Javascript/Node, Go, and now Rust.

The vibe coding people seem to be the ones that are usurping Rust's fan boi noise at the moment - every other blog is telling people how great the tool is, or how terrible it is.

namuol · 1h ago
> Everyone should learn Rust.

I know this feels like a positive vibe post and I don’t want to yuck anyone’s yum, but speaking for myself when someone tells me “everyone should” do anything, alarm bells sound off in my mind, especially when it comes to programming languages.

noman-land · 4h ago
Got recommended learning paths? I tend to prefer follow along adventures via video.
maxbond · 4h ago
Check out Jon Gjengset.

https://www.youtube.com/@jonhoo

sesm · 4h ago
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.
jandrewrogers · 3h ago
Rust has better defaults for types than C++, largely because the C++ defaults came from C. Rust is more ergonomic in this regard. If you designed C++ today, it would likely adopt many of these defaults.

However, for high-performance systems software specifically, objects often have intrinsically ambiguous ownership and lifetimes that are only resolvable at runtime. Rust has a pretty rigid view of such things. In these cases C++ is much more ergonomic because objects with these properties are essentially outside the Rust model.

In my own mental model, Rust is what Java maybe should have been. It makes too many compromises for low-level systems code such that it has poor ergonomics for that use case.

pjmlp · 5m ago
Java should have been like Modula-3, Eiffel, Active Oberon, unfortunately it did not and has been catching up to rethink its design while preserving its ABI.

Thankfully C# has mostly catched up with those languages, as the other language I enjoy using.

After that, is the usual human factor on programming languages adoption.

Const-me · 2h ago
Interestingly, CPU-bound high-performance systems are also incompatible with Rust’s model. Ownership for them is unambiguous, but Rust has another issue, doesn’t support multiple writeable references of the same memory accessed by multiple CPU cores in parallel.

A trivial example is multiplication of large square matrices. An implementation needs to leverage all available CPU cores, and a traditional way to do that you’ll find in many BLAS libraries – compute different tiles of the output matrix on different CPU cores. A tile is not a continuous slice of memory, it’s a rectangular segment of a dense 2D array. Storing different tiles of the same matrix in parallel is trivial in C++, very hard in Rust.

winrid · 42m ago
Hard in safe rust. you can just use unsafe in that one area and still benefit in most of your application from safe rust.
Ar-Curunir · 2h ago
> However, for high-performance systems software specifically, objects often have intrinsically ambiguous ownership

What is the evidence for this? Plenty of high-performance systems software (browsers, kernels, web servers, you name it) has been written in Rust. Also Rust does support runtime borrow-checking with Rc<RefCell<_>>. It's just less ergonomic than references, but it works just fine.

jandrewrogers · 2h ago
Anyone that works on e.g. database kernels that do direct DMA (i.e. all the high-performance ones) experiences this. The silicon doesn’t care about your programming language’s ownership model and will violate it at will. You can’t fix it in the language, you have to accept the behavior of the silicon. Lifetimes are intrinsically ambiguous because objects have neither a consistent nor persistent memory address, a pretty standard property in databases, and a mandatory property of large databases. Yes, you can kind of work around it in idiomatic Rust but performance will not be anything like comparable if you do. You have to embrace the nature of the thing.

The near impossibility of building a competitive high-performance I/O scheduler in safe Rust is almost a trope at this point in serious performance-engineering circles.

To be clear, C++ is not exactly comfortable with this either but it acknowledges that these cases exist and provides tools to manage it. Rust, not so much.

lenkite · 9m ago
New DB's like Tigerbeetle are written in Zig. Memory control was one of the prime reasons. Rust's custom allocators for the standard library have been a WIP for a decade now.
abirch · 4h ago
I think the major decisions behind Rust is being explicit and making the programmer make decisions. No NULLs, no Implicit conversions, no dangling pointers. Lifetimes, Optional, Results, each Match branch needs to exist, etc.

Side note: Stack allocation is faster to execute as there's a higher probability of it being cached.

Here is a free book for a C++ to Rust explanation. https://vnduongthanhtung.gitbooks.io/migrate-from-c-to-rust/...

sesm · 3h ago
> being explicit and making the programmer make decisions

Why RAII then?

> C++ to Rust explanation

I've seen this one. It is very newbie oriented, filled with trivial examples and doesn't even have Rust refs to C++ smart pointers comparison table.

landr0id · 3h ago
>> being explicit and making the programmer make decisions

>Why RAII then?

Their quote is probably better rephrased as _being explicit and making the programmer make decisions when the compiler's decision might impact safety_

Implicit conversion between primitives may impact the safety of your application. Implicit memory management and initialization is something the compiler can do safely and is central to Rust's safety story.

NobodyNada · 4h ago
This might not be exactly what you're looking for, but I really like "References are like jumps": https://without.boats/blog/references-are-like-jumps/
Waterluvian · 5h ago
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.

namuol · 1h ago
I’ve found emulators to be a pretty poor first project for rust specifically for the reasons you alluded to: That you need to know to write it without heap allocation (or other hoop jumping so long as you avoid juggling lifetimes) when so much literature and example emulator code doesn’t do this is a recipe for a bad experience. Ask me how I know.

If you’re going to write an emulator in this style, why even use an imperative language when something like Haskell is designed for this sort of thing?

ajross · 3h ago
> 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.

landr0id · 3h ago
So long as you're aware that you're not optimizing, it's fine. Trying to build something useful as a new Rust dev while worrying about lifetimes is going to be quite challenging, unless your intention is to specifically learn about lifetimes and the borrow checker.

Yes the borrow checker is central to Rust, but there are other features to the language that people _also_ need to learn and explore to be productive. Some of these features may attract them to Rust (like pattern matching / traits / etc.)

rafram · 2h ago
Not to mention that even though you can always refactor later, will you really? It’s much easier not to.

In my experience, hobbyist Rust projects end up using unwrap and panic all over the place, and it’s a giant mess that nobody will ever refactor.

mikepurvis · 1h ago
I went through this the first year that I did Advent of Code in rust, like okay I read in all the strings from the input file and now they're in a vector, so I'm going to iterate the vector and add references to those strings into this other structure, but of course they're still owned by the original vector, that's awkward. Oh wait I can iter_into and then I get owned objects and that ownership can be transferred to the other structure instead, but now I need them to also be keys in a map, do I use references for that too?

Cloning small objects is lightning fast, turns out in a lot of these cases it makes sense to just do the clone, especially when it's a first pass. The nice thing is that at least rust makes you explicitly clone() so you're aware when it's happening, vs other languages where it's easy to lose track of what is and isn't costing you memory. So you can see that it's happening, you can reason about it, and once the bones of the algorithm are in place, you can say "okay, yes, this is what should ultimately own this data, and here's the path it's going to take to get there, and these other usages will be references or clones.

ajross · 1h ago
> Cloning small objects is lightning fast

It's really not, it's the way python works. Heap allocations are "fast" on modern CPUs that are too fast to measure for most stuff, but they're much (much) slower than the function call and code you're going to use to operate on whatever the thing it was you cloned.

Code that needs memory safety and can handle performance requirements like this has many options for source language, almost none of which require blog posts to "flatten the learning curve".

(And to repeat: it's much slower than a GC which doesn't have to make the clone at all. Writing Rust that is "Slower Than Java" is IMHO completely missing the point. Java is boring as dirt, but super easy!)

CobrastanJorji · 4h ago
Regarding the first example, the longest() function, why couldn't the compiler figure it out itself? What is the design flaw?
ordu · 1h ago
Compiler can figure that out, but the thing is compiler needs also to understand lifetimes at the site where this function is called. In general case compiler will not look into the code of a called function to see what it does, compiler relies on a function declaration.

That `longest` if defined without explicit lifetimes treated like a lifetime of a return value is the same as of the first argument. It is a rule "lifetime elision", which allows to not write lifetimes explicitly in most cases.

But `longest` can return a second reference also. With added lifetimes the header of the function says exactly that: the lifetime of a return value is a minimum of lifetimes of arguments. Not the lifetime of the first one.

mplanchard · 4h ago
You’re passing in two references and returning a reference.

The compiler knows the returned reference must be tied to one of the incoming references (since you cannot return a reference to something created within the function, and all inputs are references, the output must therefore be referencing the input). But the compiler can’t know which reference the result comes from unless you tell it.

Theoretically it could tell by introspecting the function body, but the compiler only works on signatures, so the annotation must be added to the function signature to let it determine the expected lifetime of the returned reference.

NobodyNada · 1h ago
> Theoretically it could tell by introspecting the function body, but the compiler only works on signatures

Note that this is an intentional choice rather than a limitation, because if the compiler analyzed the function body to determine lifetimes of parameters and return values, then changing the body of a function could be a non-obvious breaking API change. If lifetimes are only dependent on the signature, then its explicit what promises you are or are not making to callers of a function about object lifetimes, and changing those promises must be done intentionally by changing the signature rather than implicitly.

j16sdiz · 1h ago
> changing the body of a function could be a non-obvious breaking API change

This. Many trival changes breaks API. This is not ideal for library developers.

You can argue it is broken already, but this is forcing the breakage onto every api caller, not just some broken caller.

raincole · 3h ago
It's a design choice.

To make a compiler automatically handle all of the cases like that, you will need to do an extensive static analysis, which would make compiling take forever.

j16sdiz · 1h ago
Would be nice if an IDE can autofix it.

Maybe autofix as we type, or autofix when it save the document / advance to next line.

dmitrygr · 6h ago
> 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?

mre · 6h ago
For people who don't get the reference, this might be referring to the notoriously gnarly task of implementing a doubly-linked lists in Rust [1]

It is doable, just not as easy as in other languages because a production-grade linked-list is unsafe because Rust's ownership model fundamentally conflicts with the doubly-linked structure. Each node in a doubly-linked list needs to point to both its next and previous nodes, but Rust's ownership rules don't easily allow for multiple owners of the same data or circular references.

You can implement one in safe Rust using Rc<RefCell<Node>> (reference counting with interior mutability), but that adds runtime overhead and isn't as performant. Or you can use raw pointers with unsafe code, which is what most production implementations do, including the standard library's LinkedList.

https://rust-unofficial.github.io/too-many-lists/

Animats · 5h ago
Rust still needs a way out of that mess. It's conceptually possible to have compile time checking for this. Think of RefCell/Weak and .upgrade() and .borrow() being checked at compile time.

I've discussed this with some of the Rust devs. The trouble is traits. You'd need to know if a trait function could borrow one of its parameters, or something referenced by one of its parameters. This requires analysis that can't be done until after generics have been expanded. Or a lot more attributes on trait parameters. This is a lot of heavy machinery to solve a minor problem.

bigstrat2003 · 26m ago
> Rust still needs a way out of that mess.

It has one: use raw pointers and unsafe. People are way too afraid of unsafe, it's there specifically to be used when needed.

umanwizard · 3h ago
> Rust still needs a way out of that mess.

In practice, it really doesn't. The difficulty of implementing doubly linked lists has not stopped people from productively writing millions of lines of Rust in the real world. Most programmers spend less than 0.1% of their time reimplementing linked data structures; rust is pretty useful for the other 99.9%.

Animats · 1h ago
Doubly linked lists are rare, but backlinks to the owner are often needed. It's the same problem, mostly.
sbrother · 2h ago
Apologies since I have not taken the time to learn rust yet, but I've written a lot of modern C++. Is the ownership model kind of like std::unique_ptr and std::move, and `Rc<RefCell<Node>>` the same idea as `std::shared_ptr`? But less idiomatic? Or do I have the wrong idea?
khuey · 1h ago
Not really, because Rust enforces a "many readers or one writer" invariant on everything that has no C++ equivalent. That invariant is precisely what makes the doubly-linked list case hard (because every interior node in the list would be readable from two places, which means it can never be written to).
worik · 3h ago
I am working on a code base, that among its many glories and poo balls every list is a doubly linked list.

Stop!

If you are using a doubly linked list you (probably) do not have to, or want to.

There is almost no case where you need to traverse a list in both directions (do you want a tree?)

A doubly linked list wastes memory with the back links that you do not need.

A singly linked list is trivial to reason about: There is this node and the rest. A doubly linked list more than doubles that cognitive load.

Think! Spend time carefully reasoning about the data structures you are using. You will not need that complicated, wasteful, doubly linked list

dmitrygr · 2h ago
> There is almost no case where you need to traverse a list in both directions

But you might need to remove a given element that you have a pointer to in O(1), which a singly linked list will not do

dwattttt · 2h ago
If that's a specific use case you need to handle, it's O(1) again if you have a pointer to both the node to be removed and the previous node.

Whether it's more efficient to carry a second pointer around when manipulating the list, or store a second pointer in every list node (aka double linked list) is up to your problem space.

Or whether an O(n) removal is acceptable.

MeetingsBrowser · 2h ago
Getting the pointer to that element means randomly hopping around the heap to traverse the list though.

Linked lists are perfect for inserting/deleting nodes, as long as you never need to traverse the list or access any specific node.

pornel · 6h ago
So that you learn that loaning is for giving temporary shared^exclusive access within a statically-known scope, and not for storing data.

Trying to construct permanent data structures using non-owning references is a very common novice mistake in Rust. It's similar to how users coming from GC languages may expect pointers to local variables to stay valid forever, even after leaving the scope/function.

Just like in C you need to know when malloc is necessary, in Rust you need to know when self-contained/owning types are necessary.

mplanchard · 4h ago
The biggest thing I’ve run into where I really want self-referential types is for work that I want to perform once and then cache, while still needing access to the original data.

An example: parsing a cookie header to get cookie names and values.

In that case, I settled on storing indexes indicating the ranges of each key and value instead of string slices, but it’s obviously a bit more error prone and hard to read. Benchmarking showed this to be almost twice as fast as cloning the values out into owned strings, so it was worth it, given it is in a hot path.

I do wish it were easier though. I know there are ways around this with Pin, but it’s very confusing IMO, and still you have to work with pointers rather than just having a &str.

dwattttt · 6h ago
I'd rather pair program with someone wary of double-linked lists, but is really hot on understanding ownership than the other way around.

No comments yet

Ar-Curunir · 2h ago
Because most of my code is not doubly-linked lists!
lmm · 3h ago
Because you care about productivity and safety more than l33t h4x0r hazing rituals?
worik · 3h ago
Surrender! to compile

Weather the ferocious storm

You will find, true bliss

woah · 6h ago
Does anyone still have trouble learning Rust? I thought that was kind of a 2015 thing
lenkite · 4m ago
Unfortunately, yes. I still end up writing C++ instead of Rust for low-level system stuff. Since I also know Go - I usually prefer that when I need lean, middleware services. Learned Rust (somewhat) with great difficulty but still don't use it anywhere. Still haven't figured out how to design effectively using Rust and approaches suggested in that article like clone()/unwrap() stuff and refactor later just leave a bad taste in that mouth.
SoftTalker · 5h ago
For me it was like Haskell. I spent an afternoon on it, my brain hurt too much, and I relegated it to the category of languages that are too complicated for what I need to do with a computer.

Languages I liked, I liked immediately. I didn’t need to climb a mountain first.

mre · 6h ago
The thing is, once you internalized the concepts (ownership, borrowing, lifetimes), it's very hard to remember what made it difficult in the first place. It's "curse of knowledge" in some sense.

What's changed since 2015 is that we ironed out some of the wrinkles in the language (non-lexical lifetimes, async) but the fundamental mental model shift required to think in terms of ownership is still a hurdle that trips up newcomers.

echelon · 5h ago
100%. Newcomers still struggle a bit, especially if they've never used C/C++ before.

A good way to get people comfortable with the semantics of the language before the borrow checker is to encourage them to clone() strings and structs for a bit, even if the resulting code is not performant.

Once they dip their toes into threading and async, Arc<Lock<T>> is their friend, and interior mutability gives them some fun distractions while they absorb the more difficult concepts.

mre · 5h ago
Do you mean `Arc<Mutex<T>>`? Yeah, I agree. Wrote a blog post on that topic as well: https://corrode.dev/blog/prototyping/ The title is a bit of a misnomer, but it's about beginner-friendly escape hatches in the language. Perhaps it's useful to newcomers.
echelon · 4h ago
Any lock, but that's generally the best choice.

Great post! It's got a ton of advice for being productive, and it should be especially useful for beginners.

whobre · 3h ago
Coming from C++, I don’t find it hard to learn. I do find it annoying, don’t love the syntax and absolutely hate cargo.

To each his own, I guess….

LAC-Tech · 3h ago
Yeah I struggled with it.