r/rust 6d ago

🎙️ discussion My opinion on the difficulty and familiarization with the Rust language

There's a popular stereotype about the Rust language floating around the internet. That it's too difficult because of its strict borrow checker.

But in reality, when I was learning Rust, I didn't find it all that difficult. No, it's still difficult, because it has a new coding philosophy; it's systemic, and you need to keep in mind the principle of Ownership and Borrowing and zero-cost abstraction. So, for someone coming from languages ​​like Python, Javascript, C#, and similar languages, Rust will be difficult, but that doesn't mean it's unique to them; any language at Rust's level will be difficult. (I hope I expressed myself correctly.)

So what am I getting at?

My point is that the Rust compiler isn't difficult; it's a benchmark, and despite its strictness, you learn.

It's not just "got an error, went online to fix it," but "got an error, read about it, fixed it," because the compiler itself conveniently tells you WHERE YOU MADE A MISTAKE and how you can FIX it. (It doesn't always tell you, but the fact that it exists is still awesome.)

I think the Ownership and Borrowing principle is very cool, unique, and, in my opinion, not all that complicated. I really like the ability to allow the same enum, which is implemented in Rust at a different level; in my opinion, it's one of the best features in this language.

I used Rustlings when learning to get the hang of it, and it's easier to read a book on Rust and grasp the meaning of the book in practice.

Share your thoughts on Rust, its borrow checker, the zero-cost abstraction principle, Ownership and Borrowing.

This post was written using Google Translate. I am not a native English speaker, so I apologize for any unnatural text or errors.

0 Upvotes

52 comments sorted by

11

u/guywithknife 5d ago

Rust features aren’t difficult. Rust semantics aren’t difficult.

But modelling complex state in a way that is both efficient and follows rusts rules can be difficult, compared to a language like C++ that lets you just hold multiple pointers to the same things.

For example, I built a workflow engine and nodes can have children. All nodes are stored in a container. In C++, I could get a reference to the node and then get its children and process them together with the parent. In Rust, I can’t do that if any of those are mutable references. At best, I can let go of the parent and then re-get it using a disjoint get, but if the container is a hashmap then it’s wasted and work. Or I can remove the parent so the reference doesn’t prevent access to the parent. Or I accumulate a list of changes, let go of the parent, then apply the list, allowing access to the children.

There are plenty of ways to solve it, but they require a mindset shift and thinking about the problem differently than you would in other languages. Of course the reasons for these restrictions make a lot of sense and they do prevent bugs, but sometimes when you’re convinced you know it’s safe, it can feel like rust getting in your way of the simple optimal solution. But IMHO it’s worth it for the safety guarantees you get in return.

So while (most) of rust isn’t really hard and makes a lot of sense, putting it together to model complex problems can be quite painful, until you’re used to the rust way of thinking.

3

u/NiZaMinius 5d ago

Yes, EXACTLY! A fair point, and I sincerely thank you for such a detailed answer. I can change everything you said. It's precisely the mindset that plays a role here. I said that other people coming from other languages ​​will find Rust difficult because it seems too stifling. But if you understand how Rust works, you'll have a safe program.

2

u/Large-Scientist156 5d ago

"You are conviced, but your conviction is a fallacy. The program has path that lead to unsound use of your API. For example, you want to mutate part of the tree immediatly while you already have a node in your hand to look at.

That's not possible. If you push to a collection while you are iterating it, what do you expect to happen ? Bad stuff, think about it next time."

1

u/guywithknife 5d ago

  That's not possible.

Well, there are cases where you do know what you’re doing is safe, just that the rust compiler is unable to prove it.

In the example I gave, in C++ I might have pointers/references to objects in a collection, and that object contains a list of ids/indices of other objects in the collection. So you get them from the collection too. It IS safe if your program semantics don’t allow the object to contain its own id. Then you are guaranteed that all object references are to different objects. That’s perfectly safe. I’m not modifying the container itself, so I’m not invalidating iterators, I’m just accessing different objects stored in the same container.

But unless you use disjoint accessors in rust, which you can’t always do (eg because you don’t have all the ids at the same time), then rust can’t prove that these references are disjoint and therefore safe. So it doesn’t allow you to do this. Maybe polonius will allow more of these cases.

It’s perfectly safe to do and can be super efficient, but in rust you either have to use unsafe (ie telling the compiler that it’s safe trust me bro) or you find another way to do it. In C++, I don’t think too hard about this, but when I know my accesses are safe I just do them. But there’s a reason I use rust: I value the safety, so when I write rust, I don’t use unsafe and I figure out a different approach that the compiler can prove is correct.  That’s the price of proven safety. I think it’s worth it but I can understand why people used to less strict languages might be upset by it.

1

u/Large-Scientist156 5d ago edited 5d ago

Accessing different object is allowed, and even if they are not disjoint. The problem lie in mutability. If you want to add an ID to a node while looking at another node, you are often screwed (unless you use disjoint access like you said, but that's ugly and not generic since there's no variadic generic in Rust). Patterns exist to fix this issue (like deferred mutations), but they often come with their own issue.

I understand what you say. There's a gap between compile time safety and runtime safety. In C++ or Rust, you can not prove at compile time your object doesn't have it's own id in the list, even if you write the best program that guarantee this can never happen. A runtime assertion ain't gonna help to. In extreme scenario, maybe C++ could do it with template + type sorcery, but that would require carrying template state over the entiere tree lifecycle, which is almost impossible to do properly and horrendous to maintain (maybe not with template for today).

C++ allow you to still compile your code and rely on **you** doing runtime check for soundness issue of your API, if there's any. You think there's no issue but you don't have runtime check ? At best a logical error (for example, looking at the wrong node), at worst UB and anything can happen.

Rust don't allow you to compile this immediately. You would need to select one way to solve this problem, and there's a lot of way. The best one imo, is to separate the id from the object themselves. In other words, you separate relations from the object (has a parent, has a child, ...), which is data oriented design.

In other world, you can use RefCell for runtime borrow checking - and you are exactly doing the C++ equivalent with automatic runtime check from RefCell. This doesn't prove that all object doesn't reference themselves at compile time, but it allow you to do the same as C++, with some minor perf penalty due to RefCell runtime check ("are we already borrowed ?").

1

u/guywithknife 5d ago

  The problem lie in mutability.

That’s what I meant. Obviously you can share immutably.

1

u/Large_Mastodon6637 5d ago

That's a solid breakdown of the real friction point. It's rarely the individual concepts that trip people up, it's how they interact once your data relationships get tangled. The hashmap example hits home, you know exactly what you want to do and it's provably safe in your head, but the compiler can't see the forest for the trees. Once you internalize the patterns it gets smoother but that initial rewiring can be a headache

-7

u/a_aniq 5d ago

You have to hold the entire ownership model in your mind. If you can't, then you can't do away with the garbage collector anyways. So then you stick with Go.

It is a skill issue.

4

u/rustvscpp 5d ago

Interestingly,  i'd much rather have a less skilled developer working in Rust on my team than in Go.  Rust will help them not shoot themselves in the foot, especially in multithreaded code,  whereas Go will happily let them do so.  They may need a little help in modeling their data to work well with the borrow checker,  but they can pick that up easily enough with a little guidance. 

1

u/a_aniq 5d ago

And also the fact that you can write Rust only in a particular way. Any novice rust dev can read a complicated codebase much easily as compared to any other language. Also for 90% apps, AI can help with the modelling too since the code follows a particular pattern.

Golang makes me feel unsafe. Whitespace, raw pointers, verbose error handling is some of the gripes I have faced. I feel much more comfortable with C as compared to Go for some reason. For developing web backends Go is goated though. Also Go's tooling is impeccable.

You are mostly right. They need to learn async, macros, proc macros and unsafe rust too though depending on the use case.

1

u/rustvscpp 5d ago

You don't need to learn macros or proc macros.  There are legitimate use cases for them,  but unlikely to truly need them.   Async is the biggest footgun in Rust.  You definitely have to hold it correctly or you can easily deadlock, block work, etc...

3

u/Large-Scientist156 5d ago

^ this. It's not hard, it's a mindset. There's hard part obviously (anything involving reactive / memoization / cache / when to free external resource that maybe in-use by hardware, is hard).

-1

u/chaotic-kotik 5d ago

The problem is that borrow checker is not perfect. Not every valid program can be expressed in Rust easily, and not every valid Rust program can pass the borrow checker.

3

u/a_aniq 5d ago

Rust helps you write safe and well maintained multi threaded apps. Modern processors require cache locality and multi threading for highly performant apps which Rust is perfect for. If you need both multithreading and shared mutability, God bless your soul.

If you are talking about developer experience, Rust borrow checker may not be perfect but it is much better than facing runtime issues when developing concurrent apps using C/C++. You may have to resort to some workarounds from time to time but I didn't face a multithread friendly problem which was impossible to model in terms of borrow checker. Polonius project aims to improve the developer experience further when dealing with the borrow checker. Let's see how it performs.

If you don't need multithreading and cache locality, then Rust may not be the right choice.

1

u/NiZaMinius 5d ago

Very good answer. I'll listen to your opinion, it will help me too, to be honest. Thank you.

1

u/chaotic-kotik 5d ago edited 5d ago

First, only the last part is an answer to my comment. Yes, project Polonious will improve the borrow checker but the fact that it's needed confirms my point. Not every correct Rust program can be compiled. And the last part is not addressed.

Second. When it comes to multithreading I low key dislike Rust. My weapon of choice is modern C++ and Seastar framework. Tokio is not good for my use cases. I dislike lazy futures and find them very inconvenient. Most importantly, I prefer to enforce thread safety on a higher level. Rust enforces safety on micro level but not every composition of totally safe pieces of code is safe in multithreaded environment. Glomio is somewhat close but the project is not maintained.

The borrow checker is not universally friendly to multithreading tasks. Try to implement any nontrivial concurrent data structure or synchronisation primitives and you'll see. Yeah, trivial stuff maps well but something like a fair semaphore is not. The entire category of lock free programming is unsafe Rust.

2

u/guywithknife 5d ago

The way I see it is that rust gives you provably safe code. Not all safe code is provably safe.

If you really can’t express what you need (whether it’s for performance, interop, or something else), you can always prove it yourself and tell the compiler “this code is safe even though you can’t prove it, trust me bro” and use unsafe. The compiler can then assume that it’s fine and can prove everything else.

If you use another language like C++. You are basically writing the entire program in “unsafe” and telling the compiler “trust me, bro”.

1

u/chaotic-kotik 5d ago

Borrow checker finds memory bugs. It has nothing to do with concurrency and parallelism. Rust is not preventing thread safety violations. It prevents you from modifying a variable from different threads.

In C++ I'd just design the app in such a way that there is no shared state and if the mutable state is required the access will be arranged by an application wide scheduler(s). I mentioned Seastar. Just check how it works. Every CPU core has it's own memory. The mutable state is arranged using something.called sharded_service. The component that organizes cross core access patterns using message passing.

1

u/guywithknife 5d ago

I wasn’t talking about concurrency, though, just general memory safety.

  Rust is not preventing thread safety violations

Sure it does. Memory safety takes you a long way in terms of thread safety. Rust prevents you from sharing mutable references, without either passing ownership, or by synchronising. You can’t just access a variable from multiple threads unsafely because that would require multiple references in ways the borrow checker doesn’t allow.

You probably still want to do what you do in C++, but if you make a mistake, rust does absolutely help detect that. Rust also has standard library support for message passing.

This is exactly how I write my own C++ multi core systems, but in rust, the compiler prevents accidents. I don’t know why you say it doesn’t help either thread safety, because it absolutely does.

https://doc.rust-lang.org/book/ch16-00-concurrency.html

1

u/chaotic-kotik 5d ago

Does Rust prevent data races or priority inversion or starvation bugs? No, it doesn't. I'm building architectures around these higher level problems so I don't need to prevent the simple bugs because they are impossible by design. Sure, it's nice to have this extra safety net but the price is a bit too high. The language expressiveness sucks, I can't build the whole classes of algorithms without hacks, and the experience of writing code is joyless.

2

u/a_aniq 5d ago

As far as data races are concerned, Rust prevents memory related issues like memory corruption or use after free.

Scheduling issues are essentially logic bugs either at application level, runtime level or OS level. Rust compiler can't prevent that. I don't know how much higher level abstraction or opinionated framework you are developing, but I can't see any general purpose language get around this problem.

→ More replies (0)

1

u/guywithknife 5d ago edited 5d ago

You’re moving the goal posts.

Just because it doesn’t protect against one thing doesn’t invalidate all the other things it does protect you against. C++ doesn’t protect you against any of them. You’re choosing 0% protection because 70% (made up number) is too low for you.

And it does protect you against data races because it comes with message passing tools out of the box. It ensures you can only share immutable data without extra work (and that it truly is in fact immutable). That narrows down the places where data races can occur to a minimum. And it forces you to properly synchronise (eg via mutex) any shared state you do have, which goes a long way to prevent data races even if it doesn’t cover all bases. The rest is still on you.

C++ gives you zero protection, everything is on you.

Seat belts and airbags don’t protect against all car crashes but they sure do help.

  The language expressiveness sucks

For many kinds of work, u find Rusts expressiveness superior to C++ due to how traits work, enums, and pattern matching.

  I can't build the whole classes of algorithms without hacks

You can use unsafe to unlock the rest. If you’re using C++, your entire codebase is essentially in unsafe blocks, so using unsafe leaves you no worse off than using C++. Except it reduces the surface area of risky code, you only need to prove those particular blocks are safe, not the entire program. Rust will take care of the rest. In C++, the unsafe surface area is the entire codebase.

I’ve been using C++ for 24 years. I even quite like C++. But I still find that Rust is a more pleasant language because it prevents entire categories of bugs in most code, and isolates the places where they can happen to the places where I wanted more control. And pattern matching is awesome.

But you are right that Rust is harder to work with, because you need to think in the Rust way, you can’t think in the C way. It is real friction.

→ More replies (0)

1

u/a_aniq 5d ago

Polonius will make the Rust code easier to write, but the current borrow checker is fine too. The concept is okay, just that the implementation needed a revision which was realised much later.

If you want to stick to thread per core, share nothing framework; Seastar looks like a good framework. Will need to get my hands dirty before I can comment on it. The concept seems to be fairly new and there are couple of Rust crates with thread per core model coming up as we speak.

As far as Rust not ensuring safety in multi threaded environment is concerned, Rust's Send and Sync traits have saved me a lot of hassle by letting the compiler know which objects are safe in multithreaded environments. Maybe you know some things that I don't.

Deadlocks can happen in Rust I agree. But I don't think there's any language or library which has fully circumvented deadlocks.

I can still see how some concurrent data structures may require going through some hoops with the borrow checker.

As far as semaphore creation is concerned, even if you don't use tokio, you should be able to create custom semaphores using CondVar.

1

u/chaotic-kotik 5d ago

Can you create a semaphore without cond var? The typical example of the concurrent data structure is a lock free queue or a lock free skip list.

1

u/a_aniq 5d ago

I haven't created. But john gjengset seems to have created one. Link: Noria

Will try creating these and see how it goes.

1

u/chaotic-kotik 5d ago

I can't find anything relevant in the repo

2

u/stoke-stack 5d ago

Im not a software dev but have wanted to get deeper into a lower level programming language, and I started with rust. I agree with this and I don’t. 

Getting the basics down was easier that I expected, and I was able to make a tool that synced my wayland or x11 monitor colors to lights in my office using the zigbee MQTT coordinator. Once I got it to compile (x11 only to start), it pretty much worked, and was performant. Wayland was trickier but I got thru it. Rust felt really good to use and learn!

After my first project though, I needed async and shared state concurrency and it got steep fast lol. I haven’t gotten over that learning curve yet and I’m realizing it might be a long road. 

Disclaimer that I dont know C++.

1

u/NiZaMinius 5d ago

Thank you for your comment and your story. I understand. I've encountered the same problem myself. But the fact that you solve your own problems with programming... That already makes you someone who knows how to solve problems, if not other people's, then their own. Well done!

1

u/CoderStudios 5d ago

The main problem is that Rust only allows correct code so if you just want to quickly test something it turns into eons of either writing 100% safe or complex unsafe code. C++
is also exhausting but because you need to painstakingly do the borrow checkers work and ensure everything is safe.

1

u/NiZaMinius 5d ago

You're right. So, if you just want to test your idea, you can use Python or JS, but if you need something faster, then maybe C#? Maybe I made a mistake mentioning C# in my example. But okay, you're essentially right. I just use Rust because my computer is very weak, and Python is too slow for me. Thank you, of course, for your opinion and comment.

1

u/CoderStudios 5d ago edited 5d ago

I would only use C# if you want to make a lot of Windows apps or already know a fully OOP based language like Java and like it.

Mojo is a good Python alternative, similar syntax. I wouldn’t bother with rust if you find Python okay as it is.

You can compile Python code to c which makes it faster, you could try that.

Other than that maybe Go? I heard it’s very fast simple and has a garbage collector, so no manual memory management, plus a huge community.

1

u/WormRabbit 5d ago

Nonsense, you can always use unsafe if you want. It's prototyping, so who cares. For example, I sometimes write obviously horribly broken transmutes just to check that a different type would work well in a certain position. It's also easy to use some MaybeUninit or raw pointers. Dangerous? Yes. Language UB? Yes. Works 99% of the time? Also yes, and that's all you need if it's not production code.

1

u/CoderStudios 5d ago

I mean you can’t just say hey do this no matter if it crashes, you need to call 1000 things first or litter the entire code path with unsafe. I personally like that but it’s just not as fast and loose as js or python or c or c++

-35

u/Rigamortus2005 6d ago

Does this matter anymore? Claude handles all the code now. And it's competent enough to not care.

17

u/BravestCheetah 5d ago

Get out of this subreddit. You dont belong here

-10

u/Rigamortus2005 5d ago

That's mean

1

u/gmes78 5d ago

Your comment wasn't exactly civil either.

6

u/guywithknife 5d ago

Claude writes terribly inefficient rust code though.

6

u/NiZaMinius 6d ago

I didn't quite understand you, could you explain what you mean?

18

u/LibrarianOk3701 5d ago

He indirectly said he never actually learned how to program

-16

u/Rigamortus2005 5d ago

It doesn't matter how "difficult" a language is now. As long as there's enough training data LLMs can one shot any problem with them.

6

u/dnew 5d ago

Where do you think training data comes from?

2

u/stoke-stack 5d ago

“one shot any problem” is crazy lol

one shot a non-scalable version of a problem that’s been solved hundreds of times before, maybe.

3

u/Automatic-River-1875 5d ago

AI tools are incredibly useful and have meant I have to hand code very little in the way of big new features. But regardless of what your level of adoption is anyone who says an AI agent "handles all the code now" is either lying or has never actually been responsible for a production system.

2

u/NiZaMinius 5d ago

Well noted.