Hacker News new | past | comments | ask | show | jobs | submit login
Frustrated? It's not you, it's Rust (fasterthanli.me)
64 points by todsacerdoti on Aug 14, 2020 | hide | past | favorite | 31 comments



Long but great article. Echoes my own track record with Rust.

I've been rewriting a project in Rust. The original is a 20+ years C project (not created by me). Rewriting it in Rust has been a delight. So many surprises, discoveries, learnings.

I've first converted the codebase using C2Rust (surprisingly efficient). The code compiles and runs correctly. While Rust has been nagging me nonstop when I try to do things the original C way, and I still have a lot of weird flags on the code, once I rewrite some piece of code in idiomatic Rust, I end up with a much better design than before. Gone are weird linked lists, crazy custom hashmaps, strings regurgitated in memory, "buffers" holding any manner of data anywhere in memory. Instead, the new code is more efficient, more understandable, and of course safer - even if more restrictive!

I've found at least two major hidden bugs on the original codebase doing so too.

Rust certainly has a learning curve. It's not C/C++ nor Java/C#/JavaScript. But I'm more and more convinced it's the right way to do things in what's currently the C/C++ space.


Surprisingly thin endorsement. The central argument seems to be that one has gotten rid of custom data structures and raw buffers, but these are typical for C and of course any language with higher abstraction power and a standard library will have them in there.

In this sense you could have switched from C to almost anything else and have gotten similar benefits. Finding bugs and improving the design is typical for refactoring and redesign projects.


The point here is using types that are still fast and memory efficient without having to resort to brittle pointer magic full of gotchas. You still have raw buffers, and they're still efficient, but you just don't have to do malloc or memcpy all over the place to make them work the way you want.

The way I see it, much of Rust, and this project specifically, is not about moving to a higher abstraction... It's about moving to the right abstraction.

Yes, I could have switched to just about anything else, and end up with an even higher level code. But that would defeat the purpose of keeping it small, fast, and memory conscious.


The one thing I wish I could do for people coming into Rust is hand them "Programming Rust" and say "Read this. No, I mean it."

Rust is NOT a Stack Overflow snippet language. Attempting to do so will get you into trouble.

Rust does NOT like pointer magic very much. If you try to be too clever with pointers, you will fight with Rust.

That having been said, I really don't fight with Rust all that much. I don't understand everyone complaining about the borrow checker, but, then, I've been doing embedded C for a very long time now. So, a lot of my bad habits have been ground out of me--quite often very painfully--by chasing subtle bugs in production devices.

After a couple of those experiences, you begin to write very different C code.


> Rust is NOT a Stack Overflow snippet language

That is a great line!

Bad news - I just realized that I’m a stack overflow snippet programmer.


From what I've heard, the borrow checker is much more suited to low-level programs like embedded programs, and for other kinds of programs, the borrow checker is in the way a lot more. That could be why you don't understand the complaining (if they are correct, that is).


I will also say that the compiler has gotten MUCH better about flagging what the real problem is over the last couple of years. Even 3 years ago, the error messages were much less forgiving.

I also found that people tried to solve lifetime problems with the wrong tools. Normally everybody reaches for & and mut and * in Rust but very few people pointed out that an extra set of {} in the right place was more likely to solve or illuminate the problem.


I’ve been learning rust (and my first lower-than-python language) these past six months by writing a game boy emulator (it can run Tetris!!!)

It’s been one of the least frustrating most satisfying programming experiences of my life. I don’t think there’s a better language to learn while learning how to write an emulator.

Combined with the plugin for vscode, it’s my wingman that points me in the right direction whenever I want to do something stupid. The compiler errors are amazingly descriptive for a newbie.

The limitations it puts on borrowing And sharing references forced my emulator to take on a design I hadn’t planned. And completely unaware, months later, I’m uncovering so many reasons why I’m so glad I was pressed to do it that way.


I'm not sure I understand how the design has ended up with a life of its own. Are you saying that a design emerges without the intention of the programmer just by using Rust?

What do you understand by design in this case?


Im not the brightest bulb around but I’ll try.

I wanted a design where every subsystem (ppu, apu, serial, controller, etc.) owned a reference to the singular MMU struct. This is how I’d do it in python. Makes referencing mmu easier.

Rust said no, you can’t have many mutable references. I looked around and found refcell and such. Ways to have shared mutability as long as I enforced certain lifetimes and other guarantees that I’m not going to cause memory problems by deleting what other systems expect to be there.

This was looking complicated and messy. Sure I could do it. But it spoke to me: “this is probably not right”.

I ended up with a design where I have a core “step” function that calls each system’s step function, in order, and passes in a mutable reference to MMU to each. Basically it loans out the MMU one at a time.

The result is that testing is so much easier because all my systems are stateless and my MMU holds all the state. I just assemble an MMU state the way I want for each test and evaluate how the system mutated it.

It also meant that implementing save/restore state was incredibly easy because my entire guest machine state was in one struct and all the systems were stateless so they didn’t need any logic to recover state.


Maybe it's better if beginners treat Rust the same way they would when learning a new music instrument. For most people it's better to take lessons early to avoid bad habits, and assume you know very little even if you know another instrument very well.


> You're smarter than Rust. I'm not kidding!

You're probably not. That's kind of the thing. Rather, it's not that you're not smarter, it's just that you're not used to doing things properly, and Rust forces you to do things properly.

"Leave me alone, I know what I'm doing" says beginner juggling chainsaws on a unicycle.

Ultimately it comes down to this: when you don't write down all the information a compiler needs to generate an artifact, you're relying on hidden, un-stated assumptions. Such assumptions are subject to change over time. Relying on them isn't smart, it's dumb. It makes you feel smart. Just check the CVE list.


These zero information statements and intellectually dishonest marketing is what keeps rust a fringe language.

> it's just that you're not used to doing things properly, and Rust forces you to do things properly.

Many patterns that rustc refuses aren't inherently wrong. They are because 1) you have to aid lifetime inference and memory management unlike GC languages 2) All safe patterns can't be verified by compiler, especially if threads are involved.

The cult like mentality of rust spammers on hackernews is making many people sceptic about it. Rust is actually well designed language for what it does. But stop pretending rust is God.


> Many patterns that rustc refuses aren't inherently wrong. They are because 1) you have to aid lifetime inference and memory management unlike GC languages 2) All safe patterns can't be verified by compiler, especially if threads are involved.

It's not about the patterns being wrong, it's about the patterns being verifiable and having a compiler that's on your side, not on the side of watching the world burn. The difference between C's laissez-faire approach, where no new warnings are added and anything's whatever you want it to be vs. a subset of provably correct things you can opt out of at your discretion.

Yes, it's not going to catch all edge cases, yes it's not perfect, however the idea people are behind is that it represents a fundamental change from "I'm sure the smart engineer knows what they're doing" to "hold on now, let's assume they don't, or will forget in 6 months, or some new person is going to have zero context." That model maps much better to the reality we have vs. the one we wish we had.

My opinion is hardly culty, I write ObjC at my day job at $BIGCO, I've done plenty of Swift, I've written plenty of embedded C, plenty of C++, implemented a ton of stuff in VHDL. Rust is my go-to for personal projects. It's genuinely better. Not perfect, better. And better's what I'm looking for.


Ive always wondered something: comparing to languages like Swift, is Rust really better if it takes so much cognitive overhead and architectural backflips to work around the borrow checker's limitations? Couldnt we be spending that time adding features, or optimizing the parts of the program that the profiler shows us are inefficient?

Using Rust seems like it would make sense for an extremely low-level performance-sensitive application, but do people use it for higher level things? If they do, why?


That's a very good question and deserves its own thread.

Personally, I remember an interview of Keith Haring just before he passed away; he wasn't sleeping much to put as much of his ideas out as quickly as he could, he was about to die. He said something like he'd rather spend the time creating then perfecting his art. I don't know exactly what his words were but what he said made a lot of sense to me.

The popularity of C++ keeps growing for some reason and Swift offers a great dev experience!

On top of that, the layoffs in the Servo team is quite telling about how profitable the work they've put was.


I would pick Rust if I had the choice and libraries were available.

Personally, I've found that the borrow checker for the most part doesn't get in the way. But part of the reason for that is because I've used Rust enough to know what the borrow checker expects, so I would just design things in a way that get accepted in the first place without having to think about it.

Someone new to the language won't have that prior knowledge, and will be more likely to run into the borrow checker and get frustrated because of it.

So with that in mind, I don't really find the cognitive overhead to be high when writing Rust. I also find that I like having the control over what can mutate what, and knowing that information by looking at the function signatures or variable definition. Also, Rust has all of the features I liked in Haskell when I tried it, while putting them into a package that is more familiar and comfortable to me.


People definitely use it for higher-level things, and it's nice there as well - not the same level of comfort as a garbage-collected language, but still very nice.

When writing code that isn't that performance-sensitive, cloning is often an option (and gets you past a lot of grief with lifetimes), so is reference counting, etc.

Rust's set of rules just means you can never, for example, accidentally hold a reference to a value that's been freed - it'll error out, and then you can pick whether you meant to borrow it, to share ownership with reference counting, or to clone it.


I think after a while writing the language, you're able to reason about your object memory with little friction from the compiler.

On the Rust-written projects that I work on, I rarely have significant compiler errors that impede on my efficiency in adding new features.

After a while, you know that you're mutably borrowing something, or that you're crossing a thread boundary and are carrying something not thread-safe along.

Maybe like other low-level languages there's a large learning cost, but it gets better after a while.


Not to get in the way of a good time but - your conversation with opposing viewpoints perfectly illustrates the conflict I'm trying to describe in the article.

Rust will catch mistakes you would've let through, but it'll only agree to verify a subset of all valid programs.


In my case if I were to use Rust for a personal project, that would be a clear case of thinking inside the box. I think this is because I use C++ a lot and Rust is more of the same, except with fewer libraries & domain support but safer and more restrictive.

For something where one has the freedom to pick anything they want, I'd take advantage and use something more user-friendly and with even less restrictions like Python or Swift. Although for heavy lifting I may still reach for C++ because of the massive ecosystem.

Languages like Rust or Ada with their heavy reliance on specific "annotations" to ensure safety are designed for professional teams that must fulfill certain quality attributes.


> It's not about the patterns being wrong, it's about the patterns being verifiable and having a compiler that's on your side, not on the side of watching the world burn.

Yeah, but not all programs genuinely need to be multithreaded [0] that too with memory sharing, and even in a program very few data is shared. And a lot of patterns can't be verified by compiler to be amenable for static memory management. That's a tradeoff.

I am not telling rust's cognitive overhead makes it unsuitable for everything. It definitely has a place.

But unlike what a lot of people on HN tend to say, rust is not the perfect language for everything.

Languages like OCaml deserved a significant portion of hype that rust gets now. But there is no mozilla and no HackerNews spamming taskforce behind OCaml. On the positive side, all 5 companies using OCaml pay well ;)

[1] As a user, I don't want your code to exhaust all my cores, unless it is a batch job. I'd rather urge to choose better algorithms and data structures for the job and many times parallelism is just unnecessary.


Another example from Rust community, without reading the article commenting that Rust is always correct without any bugs or error and programmer is stupid.

This attitude continuously drives me away from learning Rust, didn’t find such attitude in Haskell or Lisp community which really took computer science and art of programming forward unlike Rust, which is similar to other competing niche languages.


Sometimes I get the impression from rust advocate articles on HN that they are snobs. Not here; no way. This was well written, cheeky, thorough, and gave me a solid sneak peak at doing rust from a c/c++/go/python/Java background. The exacting standards of stopping data races at compile time is an exacting task. I think I followed most everything here, although I wondered if the crossbeam inclusion should be in language not library code ... But I don't know enough to press further. Solving problems in libraries not language probably is better.

Rust is one of few if only languages I am aware of that hit hard on data safety without a gc. That'll make it quite different to the programmer.


I'm happy you enjoyed the article! I seem to remember scoped threads were part of the standard library at some point, but they were eventually proved unsound and removed.

I'm too lazy to go find a reference right now but it shouldn't be overly hard.


To be clear, that isn't what I meant, though I can't really edit my comment for clarity. Some things don't manifest the way you intend when converting from thoughts to text.


The original writer is trying to make it appealing for beginners; there are rules, you can learn, you are smart!

He's very positive and encouraging, which shows how much dedication he puts on his teachings.

Relax


Oh, sorry for the confusion, I wasn't criticizing, I really enjoyed the article. I'm working with someone new to Rust at the moment and I'm passing it along to them, too.


That's fine, happens with all of us! Just that we need to be positive, in the end... we are all trying to figure it out, be it Rust...or CSS ;)


Actually this is not a matter of smartness and paying attention. Real world projects expanding over years and decades behave like complex systems and it can be guaranteed that they will have correctness issues.

Furthermore there's always a tension between speed and safety in C and C++ programs which make them particularly prone to these errors.

In fact there are probably many people who are competent enough to write safe C++ code (C's a lot harder), but they code in environments where this is unlikely to happen.


Hell of a good write up. Tx.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: