Is Rust-Lang hard for a beginner?

There are two kinds of difficulty here:

  • Borrow checker says that you can't use a variable because ownership has been transferred elsewhere

  • Compiler says that the lifetime of a variable is not sufficient, during concurrency programming

As I understand it - I'm just starting off on my Rust adventure - the borrow checker can be appeased at any time by cloning the data, at a cost in speed.

When I read an account of lifetimes it went completely over my head. I'm hoping that it will make more sense in time.

I think Rust will be OK if you're not trying to figure out lifetimes.

This is mostly true, but Rust also uses "Resource acquisition is initialisation" extensively which, in short form, means that for certain types, a value of that type existing in your program becomes equivalent to your program, as one inhabitant of your OS among many, owning a resource. This most obviously applies to the memory needed to store that value in the first place, but also applies to things like network sockets and file handles. The value being created means your program is assigned the resource, and then the value disappearing out of reach of your code when it's dropped/goes out of scope means the resource is released back to the OS. This is, in many ways, a really neat pattern and helps you avoid all sorts of issues to do with resource handles going out of scope but then forgetting to tell the OS you're done with the thing and that it can be re-used.

...But it does have the annoying side-effect that because the existence of the value (it's lifetime - literally the span of the program the value is "live" or accessible for) is tied to an external state of the world, you can't just Clone your way past the borrow checker, because you can't (in general) make the external world correspond to you suddenly having two of the thing. This is very annoying when you're first writing code, because it gets in the way, but it also means that the borrow checker is forcing the state within your program to match that of the external world, and catching any mistakes before it can even start to run - and it doesn't need to know anything about how any particular resource works, so long as it uses the RAII pattern. If you think about it, that's a very powerful feature for a language to have for such a simple idea.

You probably won't encouter the need to do this for a while, but it's good to be aware that, as much as it sometimes appears the borrowchecker is just being a spoilsport, developers can leverage the machinery to make correct code much easier to write, because the compiler can now catch mistakes.

1 Like

Well, you probably got lost in the big words. Lifetimes are really just a way for the compiler to tell at compile time whether specific values and references are valid or not.

The concept itself is nothing new. You had to uphold (approximately) the same requirements in C and C++, too, except that the compiler didn't check them, and violating the rules led to UB rather than compile errors.

The fundamental rules are very easy; conceptually, they can be summarized as:

  • don't use a value after it has been destroyed (unique ownership);
  • don't take a reference to a value that has the chance of becoming dangling (borrowing with a correct lifetime); and
  • don't make a single mutable reference co-exist with any other reference (no shared mutability).

As it turns out, formalizing these rules to be sound (ie., reject all incorrect code) and convenient/practical (ie., accept most correct code), and threading lifetime annotations through generic types can be tricky, but that's mostly the compiler's own business. You don't have to know how the compiler achieves this in order to write correct and idiomatic Rust code.

10 Likes

Yes, it is bloody hard. No time to waste: start immediately!

Another thing. It is a language. When you want to learn French it has little merit to start learning German first. So be careful with recommendations to learn another language first: it might not help.

1 Like

In my experience - I started developing in December last year, Between then and the start of April I was hitting studying programming and development hard (I am talking 12 hours daily 7 days a week for 4 months), prior to this I didn't even know what a html tag was. There is still a lot I need to learn about development, concepts that I have only just touched the surface with.. with that being said I spent most of that time with Javascript but really trying to learn what was happening under the hood. There are of course people who will know considerably more than me. However, I understood the stack and the heap, mutable references and what sort of data goes where(again not expert level understanding but I knew if I used an object and was accessing it in multiple places I had to be careful to prevent bugs etc.), The majority of that knowledge came from laboriously going through Javascript.Info and the eloquent javascript books. Understanding types in JS made a big difference, knowing what type each method or function returns etc, so I picked up Typescript about 2 months into my journey. I can build full stack with the help of frameworks such as express in JS, manage auth, integrate payments, all the usual stuff.

With that being said, I am still a beginner. I have only very surface level knowledge of things like Authentication, a very basic understanding of SQL and a better understanding of NoSQL.

My experience up to now is that the book has done a marvelous job of helping me understand all of the concepts, it has taught me a little more depth about memory management, its been approachable, it doesn't throw in endless complex terminology. It breaks it down in simple easy to understand terms.

If you have no experience with another language it may be hard, I can't say for sure but I can say it took me a good 2 months before things started to make sense with fundamentals in Javascript and not look like a screen full of nonsense.

But the book, combined with rustlings exercises done consistently, are honestly some of the best documentation I have read as someone relatively green in this arena.

Whichever language you go for you will have to learn about types... even in Javascript, because certain methods change the types of the data you are working on and therefore the available operations you can perform at that point in your function/method.

Disclaimer: I am only 2 weeks in but I use anki, rewrite out code examples into modules I can come back and refer to later, leave descriptive comments and place them into anki cards which will then go through in the morning before I get back to working through the book. Which has helped me massively rather than rushing through.

I think (I could be wrong here maybe someone with more experience can correct me) that rust as a first language might not be the worst idea. It is merely a set of best practices and safety features to prevent you writing unsafe code. The error messages the compiler gives you and the suggestions it provides are something that I think alone make it accessible.

Sorry if my response is a bit all over the place or comes across as anything other than sharing my experience as a beginner, I have extreme ADHD so its sometimes difficult to retain a consistent train of thought.

Overall I'd say go for it. Get error lens installed too to get the inline errors when you make a boo boo and you'll have a blast :slight_smile:

6 Likes

Hmm....

This feels a bit like "the beatings will continue until morale improves"

I am no expert teacher. But IMHO it depends where you are coming from and your experience. If you are a mathematician and know find discrete mathematics easy, then yes. If you are wondering if computing is for you, then no.

I am of the generation taught in Pascal (that old) and I am greatful for it

2 Likes

I like to say Rust is the best second language to learn, after you've learned any non-systems language like Python or Javascript. But after that, there's almost no question that Rust is the most beginner-friendly systems language, if only because C and C++ have had so little competition for so long and Rust had decades of hindsight to learn from.

7 Likes

I mostly concur, because almost everyone has to learn some Javascript and Python, anyway, like it or not. But I wouldn't rule out the possibility of learning Rust first. Just expect a fairly long road to mastery. I was pleasantly surprised with out easy it was to write working code, in my early experiences, and continue to be surprised by how good rustc error messages are, and how quickly I can fix errors as a result. I also see I had no reason to fear the borrow-checker; most fixes for borrowing problems were very easy (sometimes just add a &borrow to avoid moving the variable.)

Also, I enjoyed Rustlings, but it took me a few days to complete, and I suspect I would have struggled quite a bit to solve some exercises if I were totally new to programming and to Rust. It is good, but might not be the best beginner introduction. (I think I only struggled with the threads exercises, though.)

3 Likes

Rust is definitely hard for a beginner to learn. I mean, it's even challenging for an experienced programmer to learn Rust.

I think as a beginner, it would help not to worry about certain low-level details. For example, why would a beginner need to know that a String is a sequence of UTF-8 bytes? Or why would they care about GC pauses and its negative effect on performance?

As a beginner, one is just trying to learn to "walk". There is no need to worry about "picking the right running shoes" for best performance.

Don't get me wrong though. I do love Rust, its type system, and tooling. That's why I have spent the time to learn it. But personally it took me almost a year to feel comfortable writing Rust programs, and I have been coding over 20 years in several languages :slight_smile:

5 Likes

This is probably very subjective. I had no problem whatsoever picking up Rust, after several years of C++.

For the exact same reason everyone else needs to know: because it matters a lot. Human language text is hard, and trying to simplify it has never worked. Trying to hide the fact that strings are UTF-8 would only lead to endless, cumulative confusion about what the "length" means or what kind of "characters" an iterator over a string yields.

They don't, but there's a lot more that the memory model of the language offers that's immediately relevant for correctness. Namely, the prohibition of shared mutability. Assignment not creating deep copies in languages with implicit pointers causes a lot of confusion for beginners in those languages. Understanding move semantics is no harder than understanding that everything is magically and implicitly a pointer, and can cause action at a distance if you screw up.

10 Likes

I think that the original quote might be related to programmers who are using C++ as "C with classes" and have not come into much contact with the modern features that C++11 and above provide. I can partially understand the original statement but do agree that writing Rust feels much more streamlined and idiomatic than the blown up C++.

2 Likes

I can not be more agreed. In fact, Rust will show beginners that effectively, there are things that one really has to be aware and have in mind when coding. Those things are not just behind of a GC.

This is just an example. However, Can we say then that this is bad for beginners or it could dismiss interest on learning Rust? Of course, no. Frustration (discipline I prefer to call it in this case) is not bad in that sense, since in order to internalize concepts and comprehend how a tool like Rust works, you have no chance other than just deal with it and possibly fail at the beginning yourself. And rustc is not any abuser looking for beginners to keep them out of its yard. Simply no. Instead, it will help them all the time along the way with compiler hints or even fixes.

So I can argue that Rust will not be so easy to learn for programming beginners rather than just broadly say that It is not for beginners.

In the other hand, for beginners in Rust with programming background in other languages, the learning curve could be less steep.

I'll provide a personal anecdote, since I keep seeing the recurring theme that recommends dynamic programming languages as a first language. Hard disagree, here. Students need to learn whatever language is going to be best for them to achieve their goals.

My first exposure to programming was MIPS assembly as a teenager. My goal was to make modifications to console video games at the time. Obviously, you can't do that with JavaScript and Python. That's not to say that JS and Python experience would have been useless, just that these languages would have been an intermediate step. And not even a particularly good one. [1]

I learned JavaScript so that I could spice up my website during the Browser Wars when DHTML was a thing. [2] I learned Perl so I could create a CGI backend for it. It wasn't until decades later that I started using Python, and only because I was dealing with codebases already written in the language.

With Rust I also had a particular goal in mind. It wasn't so that I could create a better website or web app backend (though you can, if you insist). It was so that I could write software with deterministic performance. That's the goal! [3] If this language had been around when I started dinking around in video games, it still would not have been an ideal introduction to assembly language or making web sites. Just like JavaScript and Perl didn't help with game hacking. [4]

Languages have practical domains and learners need to respect that to avoid wasting time. Anyone with a particular goal in mind where Rust is applicable who is considering it as a first language should absolutely give it a try! [5] You won't be better off by learning a dynamic language first, because you'll just have to unlearn some of the bad habits they require. And unlearning is always much harder than learning.


  1. You need your 10,000 hours in a language to become proficient, and 10K hours of a dynamic language is not going to do anything for console video game hacking hobbies. Even 1,000 hours of JS or Python won't teach you all of the basics for getting into low level technical work. But just 1 hour will give you some fundamentals like "what is a variable." ↩︎

  2. Dynamic HTML - Wikipedia - Yeah, it's just a very old marketing term and predecessor for what has become the SPA. ↩︎

  3. The fact that I will struggle less as a developer using Rust is a huge selling point that cannot be overstated. ↩︎

  4. And Visual Basic didn't make me a better web developer, either. But it did help me make native Windows apps! Something that JavaScript could not do at the time, and Perl still can't. ↩︎

  5. Mind the "10,000 Hours Rule". The concept applies equally to everything. ↩︎

8 Likes

What bad habits are you thinking of? A dynamic language, using duck typing, is different, that's all. Because there is no static types, you've got to use a lot of tests - which I think is a good habit.

Building inheritance hierarchies, heavily callback-based APIs, building huge object graphs and expecting the GC to handle it, other stuff that just doesn't work or doesn't work well in Rust. They aren't bad habits in those languages because that's just how you code in Python or Javascript or whatever. But they're bad habits for someone who ultimately wants to learn Rust, which is, I think, what @parasyte was talking about. Not about bad habits for programming in general.

(Although I think there's a good argument to be made that massive object graphs are an antipattern even in dynamic languages, but there are definitely situations where it's just the path of least resistance.)

4 Likes

@trentj provided a good list of habits from dynamic languages that do not work well with Rust. I would add a few things that are not specific to Rust.

  1. First that the GC is not optional in those languages. They practically force the user to ignore allocations. This is a disastrous habit that has cost the tech industry billions of dollars [1] in every language. A large investment has gone into GC tech over the years to address this problem. It's of little concern if you can treat memory as an infinite resource, but that's unrealistic.

  2. As far as dynamic typing goes; I offer the observation made by peterkelly on HN WRT strong typing:

    This doesn't remove the need for automated testing, but does reduce the number of test cases you have to write.

    And echoed later by Ian Purton, the better articulated argument of the two:

    The Rust compiler is a thousand unit tests that you don't have to write, it's your best friend that never stops loving you.

    In the more general case, changing types at runtime is a problem that all JIT compilers for dynamic languages have to contend with (the relevant search term is "JIT deoptimization" if you are inclined). Doing this makes the code empirically slower with zero benefit.

    • Monkey patching is another example of this problem: Changing class definitions at runtime. In some cases, monkey patching cannot be avoided; Pytest, Twisted, and Gevent are prominent examples. Polyfills in JavaScript fall under this category as well, including basically every framework ever written. [2]

    • All of these issues inevitably lead to refactoring (which is a fancy word meaning "rewrite") to optimize slow functions. I genuinely believe optimizations following profiling is a good habit. But there are classes of optimizations that are entirely just working around accidental overhead that could be avoided with discipline [3].

  3. Ignoring that numbers have many different machine representations. Signed vs unsigned. Floating point vs integer vs arbitrary precision. 8-bit vs 16-bit vs ... There is no one-number-to-rule-them-all, no matter how much Number wants you to believe it. In the end, you'll have to learn about all of the BigInt and TypedArray classes anyway.

  4. Shared mutability. Controlling this beast is of course Rust's pièce de résistance. It is (by now) well understood and accepted that shared mutability is a problem even in inherently single-threaded contexts [4].

I am picking on Python, JavaScript, and Ruby quite a bit, but also taking a clear stance against Java, C#, and Go with the GC and shared mutability admonishments. It should also be noted that these are all things that a first timer won't be introduced to for quite a while. In that regard, there is a non-negligible effect of latency to understanding what's going on under the hood [5]. "It's hidden until it's a problem" is a poor abstraction.

How far can you get in Rust by ignoring number representation? If you just pick f32, that's probably good enough for 95% of cases. (Kind of like Number, right? Although f64 is a better analog.) You can always choose to learn more about the differences between primitive types later, but I don't recommend it for reasons described in the previous footnote. At least be aware of the existence of different types that seem to do the same thing. "Tradeoffs" is the name of the game in engineering!

You don't have to learn every detail about these types to use them, and I think most people who argue for learning a dynamic language first are thinking about it from their own perspective where they had to spend hundreds or thousands of hours to get a full picture of why we need things like multiple number representations in the first place. But that's disingenuous because the knowledge is mandatory regardless, and learning anything to that depth is always going to be in the hundreds-to-thousands of hours range. There are no shortcuts.

And besides, it's actually a lot of fun to learn about things like number bases, binary arithmetic, and logical connectives (boolean algebra). These concepts apply universally in programming, at every level, including electrical engineering. Ignoring these details does more harm than good, as formulated by Wirth's law.


Since you asked, that's the basic list. But I stand by my assertion that taking a goal-oriented approach to learning is the right way.

I feel bad for people who are so locked into the "just want to get things done" mindset that causes us (end users) to deal with bloated, unstable software. That's one of the things that is incredibly difficult to unlearn! You can be just as productive in Rust (or more productive) precisely because it doesn't defer inherent complexity problems to runtime crises. And hey, if you like 3 am wakeup calls to deal with production outages, fine. But I really wish that newcomers would be initiated with at least some concern for managing resources.


  1. It remains such an issue that Discord famously rewrote part of their Go stack in Rust to get deterministic performance out of the service in question. At the extremes, GC overhead can outpace the application itself, leading to the GC death spiral.

    Just think of how many engineer-hours have been wasted by deferring attention to allocations until they become service disruptions. That's not to mention downstream effects, ending up at poor user experience. ↩︎

  2. Monkey patching also exists in strongly typed languages with dynamic runtimes, like C# and Objective-C. And yes, it has many of the same downsides. ↩︎

  3. Or even with a language that enforces certain disciplines. Rust won't allow you to change types willy-nilly. Because it's a terrible, horrible, no good, very bad idea. ↩︎

  4. The Problem With Single-threaded Shared Mutability and On the connection between memory management and data-race freedom. Not to mention the entire functional programming paradigm and its immutable-only data access with pure functions, which has already been brought up in this thread by others. ↩︎

  5. This might be a stretch, but it's similar to the "latent function" concept in sociology. Latent functions are mostly ignored unless the outcome is negative. That's classified as a "dysfunction".

    The connection I am making here is that ignoring specific but nonetheless very important details (until it's too late) has a negative outcome.

    "I need to optimize this code to cause fewer JIT deoptimizations" and "I need to optimize this code to avoid unnecessary GC pauses" are symptoms of the problem caused by latency in understanding the JIT and the GC. And both examples are dysfunctions caused by bad abstractions. ↩︎

8 Likes

Hi,

yes, it's super hard for newcomers. Even if you are from a programming language background with several years of experience --rust is super hard.

The problem comes from the point that rust doesn't have garbage collection --this complicates a lot of things. 90% of my time in the current Rust project went into fixing syntax errors vs actually writing the business code. I had to rewrite the full project 3 times to finally get it working.

I spent 3months building this project. Had I used Java/Python/ Go it would have been just 1month of effort --> this should tell the scale of the learning curve.

There were several weeks I had to spend fixing 'a bug'. The forums hardly help --because for them to understand your issue, you need to be able to show it on a small scale --which itself is not always possible. Most of the time the answers in the forums are vague. Googling after that also doesn't work.

Now coming to library support:: You don't have any support for something as popular as apache kafka. Their GitHub is full of issues and nobody bothers to fix/answer them. May times you end up in non-maintained repos. Even the crates like lazy-static which is often spoken in this forum don't even bother to reply to any issue.

I had a bad experience. It could be just limited to me. I don't know. But i am putting my points here.

2 Likes

No, not really. If you don't care about efficiency, you can just copy everything and work with owned data instead of worrying about borrows (and put anything non-copiable behind an Rc<RefCell<_>>).

The problem stems much more from people having an incorrect mental model of those things called "references" in Rust, and confusing them with the "references" of GC'd languages (which are much more like Rc/Arc). That is exactly the aforementioned kind of bad habit that needs to be unlearned. If you are not willing to, and try to use Rust references as if they were GCd pointers, you are necessarily going to run into roadblocks.

It seems to me like you are having trouble expressing precisely what your actual problems are. That's certainly not the fault of the language. Several people have their problems solved daily here on URLO, and this has always been the case uniformly, ever since I joined 6 years ago. So it is definitely not the case that the majority of people are mostly unhelpful or clueless. If you are not getting precise or useful enough answers from anyone, then maybe consider that you should need to change how you are asking for help.

lazy_static is de facto deprecated in favor of once_cell::sync::Lazy. Even if it weren't, it's a crate that is pretty much done and doesn't really need any further active development. So it's understandable that the authors de-prioritize questions disguised as issues. (I'm sure actual issues about bugs or security concerns would get more attention.)

8 Likes

I think it heavily depends on said beginner's background.

From my own observation, from easiest to hardest:

  1. C/C++ programmers. They will find many Rust concept familiar, and will quickly realize what problem Rust are trying to solve. Although some may not agree with Rust's approach, they have no problem learning the language.

  2. Fresh beginners with almost no programming knowledge. I don't have too many data points on this. What I observed is that, they just accept the rule, and avoid complex mechanics like lifetimes.

  3. GC-language programmers. They often tries to relate Rust terms or concepts to their previous experience in GC-language, or force a pattern that may works well on GC language but not in Rust. These all will lead to unreasonable pain.

8 Likes

The problem stems much more from people having an incorrect mental model of those things called "references" in Rust, a

Then the model has to be fixed/ documented somewhere. I went through the Rust by Example, and Rust books. When you read all seems good. when you sit to program things go wrong. I even searched for best practices --to make sure I don't fall into un-necessary issues.

It seems to me like you are having trouble expressing precisely what your actual problems are.

No. Many are raised.

lazy_static is de facto deprecated in favor of once_cell::sync::Lazy

You missed apache kafka.