Making rust easy to learn and use

Rust is an amazing language. But the steep learning curve seems to be one of the biggest hurdles in the widespread adoption of Rust. Are there any plans for making rust easier to learn and use (without compromising on what makes rust amazing/, rust)? I watched the RustConf talk of Niko Matsakis related to making rust easier (RustConf 2021 - Project Update: Lang Team by Niko Matsakis - YouTube) and wanted to know more about it. I found a few borrow visualizers like RustViz (GitHub - rustviz/rustviz: Interactively Visualizing Ownership and Borrowing for Rust). Any details or links to read are welcome. What are the future plans, timeline and ideas for rust in this direction?

2 Likes

Obligatory anecdotal counter-evidence: I found Rust completely logical and easy to learn after years of wrestling with C++.

In my opinion, it is not Rust that is hard or has a steep learning curve. Other programming languages make it artificially "easy" to write programs that compile but then misbehave.

I think that the ultimate solution would be a change of mindset in the industry (and academia!). Alas, that is not going to happen.

In any case, you are always welcome to make suggestions on Internals. Be prepared for quite the pushback against changes to the language, though – it's been designed with much care and love, and many proposed changes have been discussed in minute detail already.

16 Likes

Same anecdotal counter-evidence. I think especially because there is so much of beginner learning materials, and documentation is much more accessible (not in the sense of manpages), comparing cpp reference docs (or this one) and rust std docs is night and day.

7 Likes

I think this is a great question to ask and something the community should definitely be talking about. However, I think the "steep learning curve" is largely to do with the lack of beginner-oriented resources for learning Rust, rather than anything inherently to do with Rust itself.

People who are new to programming often learn languages like Java, Python, or even C++. These languages, despite being popular teaching languages, have many dark corners and "gotchas" and the trick for someone teaching the language is to start with the straightforward, systematic bits and introduce the complexity gradually. You don't start a Java course with how generics are really erased, you don't start a Python course with how default function arguments are only evaluated once and you don't start a C++ course at all because that language is such a minefield there's no reasonable subset that won't get you in trouble.

Similarly, I think it's possible to teach Rust starting with basics: all value types, plenty of .clone(), rarely a & and never a 'a, and people would likely pick it up as quickly as they do Java or Python; which is to say, with a lot of fumbling and missing semicolons and misleading indentation and not realizing there's a difference between () and {} or that the order of lines in a function matters. Because it's actually not really Rust that's hard: it's programming in general that is hard, and the way Rust does things is different enough from other languages that people with prior experience get hung up on the differences rather than on the core concepts.

But for people with no prior experience, everything is hard. Understanding that String and &str are different types and you have to convert them with .to_owned() is no more difficult than when you first encounter the idea that 1 and 1.0 are, contrary to what you learned in elementary school math, actually completely different things and they might behave weirdly if you pretend they're just numbers.

Java, in fact, also has a String/&str distinction, except it's called StringBuilder/String and it's not generally considered important enough for students to learn about in their first semester. Admittedly this is partly because java!Strings are a bit more flexible than rust!&strs. But at the end of the day if you want to be an effective Java programmer you're still going to need to learn when to use a StringBuilder and this is the same in Rust. There are many things about which we tell students "Okay, this error means something you're not ready for yet, but here's how to fix it so we can move on". Rust has some of those, sure. But how much worse is it than the next language?

Bringing it back: I think it's good to talk about how to make Rust easier to learn and use. But that discussion in my opinion should be more about how do we teach people Rust in a way that makes it easy, rather than how do we make changes to Rust so that people will find it easy, which is (in my opinion) mostly a fool's errand. People by nature find different things easy and hard, and it's tough for people with prior programming experience (myself included) to relate to what would be hard for someone without such experience. The language is what it is: most of the warts can't be changed without breaking backcompat, and that's not a bad thing.

That doesn't mean I think there's nothing we could do, in a world where backcompat is not an issue, to make the language easier. In fact I have about half a dozen concrete gripes about specific warts that I think ought to work differently to make the language more consistent and easier to learn and use. However, I find these are the exception, not the rule, and don't really stand in the way for first time learners since they mostly have to do with relatively advanced topics.

With all that said, I don't think we should dismiss out of hand ideas for making the language easier. But I do think that "making Rust easier to learn" is and should be low on the priority list compared to other goals: performance, correctness, stability.

20 Likes

I think learning Rust is always going to be quite hard. It's a large sophisticated language with very powerful features that simply do take quite a while to learn - it can be somewhat overwhelming. I started writing programs in Rust over a year ago, and now feel somewhat competent in quite a few areas of the language, although not async yet ( which I haven't even tried - I know in principle what it does ). I did have a long break, but I think it just takes a lot of work and perseverance. I am quite old ( 63 year ), maybe that has something to do with it, but I don't remember another computer language (also ecosystem - crates.io) which took so much learning. It is what it is - although of course improved learning materials may help. Ultimately though the test comes when you come to write a non-trivial program, which is tough. This forum though is an incredible resource if you do get stuck.

4 Likes

Honestly, after quite a bit of time playing with Rust and being currently in the process of launching my own Rust-build server into production, I begin to feel that the "steep learning curve" is only a problem for those, who don't need Rust in the first place. The following would be elaborations of that same thought.

  • Ownership and Borrowing aren't hard, and the Borrow Checker isn't there to make your life worse.

    Your program - running on a physical, virtual, or Docker-based machine - on the most basic level, does only two things. It plays with data (which requires a processor) and it stores this data (which needs some memory). To be useful, your program will also need to handle transfers of this data, but that's a separate topic. You can think of data transfers as reading from, and writing to, a memory which isn't "yours". Whenever you program, in essence, you play and you store. That's it.

    Whenever you play with any data, you have to: 1) initialize it ("allocate" some space from memory for your playing to take place), 2) play with it (process it however it needs to be processed, transfer it somewhere or fill the memory you've pre-allocated with the transferred data from some other place) and 3) clean it (by "dropping" it, "freeing" or "deallocating" the memory, or marking the sectors of the memory that were previously used by your data as usable once again for your OS - depending on how low you're willing to go from the highest possible abstractions). Nothing else.

    As you initialize, play and clean - you need to keep track of which data is being played with by which "player" (which on the basic level is represented by a function, but can be abstracted away by introducting classes, objects, etc). Why do you need that? Because if several "players" start initializing, playing with, and throwing away the same data, you get nasty problems.

    Your OS doesn't want your players to play with data that doesn't belong to them, your players - most likely - don't want to read or write to wrong parts of memory, thinking that your data is still there, and whenever you try to introduce parallel computations in your code, you don't want for those computations to come up with strange, unpredictable results, which can creep up quite suddenly if all you have at your disposal in a toolkit of the language like C.

    Sure, you can do the same things with it, and perhaps it will even be faster - as fewer safety checks, for which Rust is known for, won't be slowing you down, but the chances of you getting more and more things wrong (by accident and not) get closer and closer to 100% the bigger your program becomes. Segmentation faults, sudden crashes, security exploits, all the beautiful things that you and your users definitely expect will keep you company for a very long time.

    You can also abstract away as much as you possibly can. You can say that any data that ever gets played with by anyone should be sealed behind 10 locks and called an "object" - which is what languages like Python and JS do. Though much easier to pick up, this approach turns out to be quite a waste of resources and "dynamic" typing for which it's famous for also tends to incorporate some hidden unpleasant surprises into the behavior of your application without your explicit intent.

    Your third approach - is to pick up Rust. It'll tell you exactly which of three steps of your coding game are written in a way that can introduce some problems, and it'll force you to think about how you can rewrite / refactor / rethink your design to make sure nothing nasty happens.

    The concept of ownership and borrowing is the way this enforcement is done. Only one player "owns" the data that is playing played with at any moment in time (meaning: only one player is responsible for keeping this data in memory and throwing it away afterwards). Only one player can "mutably/exclusively borrow" it from the owner, gaining the right to modify this data however it wishes. And if no one has an exclusive access to this data, as described above, then as many other players as you want can "take a look" at this data ("immutably borrow" it) and do something with it, provided they don't change anything in the data (and the memory that holds it). That's it.

    "Do I really need this, though?" - if you'd like to drag yourself and whomever happens to be on the receiving end of your program's end through the thrill of unexpected glitches, crashes and exploits, or by providing them with a less performant, and perhaps harder to reason about, solution, perhaps not. Stick to C in the former case, and to JS / Python - in the latter.

    Otherwise, just dig a bit deeper, understand how your machine works, understand Rust's toolkit - and perhaps become a bit more capable and knowledgeable in the process, all the while making your code more stable, secure and performant.

  • There are tons of resources.

    From the official book, to Rust for Rustaceans, to Zero to Production, to Jon Gjengset's channel - if you want to learn Rust, you can. Can it be improved? Sure. Can someone do the hard work of organizing the material for you in order to simplify things further? That's unlikely. Rust's power lies in being able to go from the bare-metal to highest level of abstractions in web servers and clients. It's neither practical, nor reasonable, to compile one giant encyclopedia for "all things Rust" - because the people that use it differ radically in terms of their fields, interests and incentives.

  • There aren't enough mature libraries.

    This is the Rust's biggest trouble at the moment. We've gotten (you've gotten, guys - I'm just an observer, really) pretty far in terms of the ecosystem, but it's still fairly limited right now. Most libraries are yet to reach the stage of 1.0 - even the most used and important ones. Beginners often get extremely confused with similar alternatives to the same problem - tokio vs async-std, rocket vs actix-web, you name it. This is where the real problem lies, but only time will solve it.

    Wider adoption can help, but at this point anyone that could discover Rust already discovered it. People who aren't using it, even though they'd like to - mostly cite the lack of eco-maturity as the main reason why. Perhaps it would make sense to compile a list of the most requested libraries / guidelines / lacking crates from possible external adopters, and work towards the minimization of the amount of items on that list? - that would require someone posting weekly here and on reddit, asking for global community assistance, and not just "hey, we'd appreciate if you could help us here". Less "it would be nice" and more "we need this next" could really improve the situation.

    That would entail a bit more proactive, even slighly pushy approach - but if the increased speed of adoption is the goal, this is the best way to go, IMHO. That would actually make Rust easy to use - import the library X for Y, and you're done. As for the learning part, it's not that big of an issue. It might help for people to get a bit more clear as to why Rust's approach makes sense, instead of focusing so much on how to learn it. As the saying goes, those who have the will, find a way - otherwise, the whole learning tends to boil down to the question of "how do I trick the compiler into not screaming at me?" - which is as bad of an approach as it can possibly be.

9 Likes

The compiler can be a good teaching resource, referencing the RustConf video on Compile-Time Social Coordination.

3 Likes

My personal experience with Rust was that it felt very hard to learn. I even gave up twice :sweat_smile:. But when we speak of a "steep learning curve", we shouldn't only consider how tough it is to make progress, but also how far we can get in short time. After mastering certain concepts, Rust allows me to write highly performant programs and/or libraries (while not creating a security nightmare) with a high level of abstraction and reusability. Learning to do this without Rust may be much more difficult.

Nonetheless, difficulty and overall complexity of the language is an issue. Perhaps some of that difficulty can be solved by improving documentation and tutorials, but I honestly think that a lot of good work has already been done. Maybe there needs to be done more, though!

Documentations and tutorials aside: I guess some aspects of the language could be changed in future as well in order to make things simpler, but I also think that is a tough job. I believe that especially in the context of async programming, a lot of features (such as async traits) need to be added to the language first before certain things can be made easier again (as these complex features need to be well-understood first).

A way out could be tutorials which focus more on a subset of Rust that is easier to use. Do I really need to understand pinning/unpinning if I only want to use async libraries? Perhaps I do. Not sure.

4 Likes

When we learn how to play a musical instrument, or to ride a bicycle, or a foreign language, there are two stages. The first stage is to learn "about" the subject. You learn some rule "you can have only one mutable reference". You learn what a musical notation means, you learn a word in the language. At that point though, you have some knowledge, but you are not yet fluent. The difficulty is not just knowing Rust's various features in theory, but being able to use them fluently, without conscious thought, without referring back to the manual. This takes time and practice, and probably mistakes. For example, you may know the rule about "one mutable reference" but it takes a while to learn the implications of that rule, when you need to use RefCell, Rc and all the other types in std. This is what takes time - becoming fluent, reaching the stage where the language features no longer feel mysterious but instead familiar and easy. It's also easy to be diverted into obscure areas like writing macros ( something you probably don't need to learn early on ), wondrous though the features are. I don't think learning Rust can ever be easy or quick.

6 Likes

I think the following service would have drastically sped up my experience learning Rust.

  1. I sign up to a site and provide my credit card; they give me some secret key that I store in an environment variable.

  2. I write some code, cargo build fails. I am confused. I type in cargo human-help $20.

  3. It packages up the current crate, posts it online, with a bounty of $20.

  4. Some more skilled Rust developer sees the $20 bounty; fixes the bug (without releasing the fix to me). System informs me someone has fixed the bug. [1]

  5. I hire them. They explain the bug fix to me. I pay them. Both sides leave reviews for each other.

[1] If they 'fix' the bug by replacing entire crate with fn main() {}, then complaint goes to mods.

2 Likes

What does it mean for a language to be easy to learn and use? What are we comparing to? Is it even fair to assume that Rust isn't already easy to learn and use? I'd like to make the case that this assumption isn't right.

–

Having already learned Rust years ago, I'd say there's no language as easy to use as Rust. There are some languages that a handful of people claim to be easier than Rust, notably Zig, but other people are not convinced. Zig's compile-time code execution does however seem to be simpler and more consistent than a bunch of features in Rust, and it would be interesting to see Rust move in the same direction, consolidating multiple features into one that is more powerful.

Comparing to more mainstream languages, I can use Python or Node JS if I want to run a couple of lines of throwaway code, and it's easier than Rust only because I don't have to create any files and remember to delete them afterwards. But as soon as the program is more than a few lines long, I inadvertently run into something that is unnecessarily complicated to get right due to the old design of these languages, and I regret not using Rust to begin with.

Do you want an exercise in obscene confusion? Try to find an answer on the web what's the right way to read a file in Java or C++. Need some therapy afterwards? Read a file with Rust.

Thinking of the difference between String and &str? Just look at the Android API, how every method that takes a string comes overloaded as six different methods, because Java doesn't have &str.

–

Some languages offer instant gratification by having an easy way to do things wrong, and then leave you on a many years long learning curve as you slowly realize your mistakes, as all code you've ever written is turned into a Whac-A-Mole game of technical debt. The world is full of this kind of bad code haunting people all the time. Apart from the vulnerabilities and bad user experiences this causes all the time, imagine how many people are spending their lives maintaining code that is bad only because someone decided to create an easy language. Making Rust more like this is clearly contrary to the aim of Rust, and even comparing the ease of learning Rust to something that doesn't offer the same quality as Rust isn't directly helpful.

The only thing in particular I hear that people are having difficulty with is borrowing and lifetimes. Keep in mind that this is a new paradigm. It isn't as much a detail as it is a paradigm. Make another language with borrowing and lifetimes, and it will probably have the same learning curve, even though it's a different language. Would you say the first language you learned was easy to learn? I generally hear from people learning their first language that programming is difficult. They don't however say that language X is difficult. They say the more general claim that programming is difficult, which is more accurate. When you learn your second language and so on, it's generally not as difficult, as most popular languages are pretty much the same paradigm. You already know programming, and you're merely learning a language. But Rust has a new paradigm, so you have to learn programming again, with this new paradigm, not just learn a language. If Rust was your first language, it might not be more difficult than any other language, and you might instead say that languages without borrowing and lifetimes are difficult and confusing.

Now consider what you get with the borrowing and lifetimes paradigm. Rust comes with static analysis included and always turned on to guarantee memory safety and absence of data races. Compare this to learning another language and to use a static analyzer or theorem prover with that language to provide the same guarantees. How many years does it take? How often does anyone do that? This is what you should compare the learning curve to. Rust is unprecedented in setting the learning curve so low that every programmer can afford a good part of this technique that was previously only accessible to safety-critical software in some big industries such as automotive and aviation. Not even your web browser is considered safety-critical enough to be proven memory safe, due to how difficult this is with other languages.

Rust already has become easier to use over the last years. There may be an old belief that Rust is difficult to use that should be reconsidered. Non-lexical lifetimes have turned out to be amazingly helpful. When I was learning Rust, the most challenging detail was combinators on futures. The type of each future described the entire flow graph of futures that are part of it, and could easily be ten lines long. You couldn't just conditionally do something asynchronously without having to wrap it in esoteric combinators to make the type of the future match the flow graph. Borrowing across asynchronous callbacks was also not possible. Futures had to be nested so state could be moved into one callback from another, or pack their variables to pass on to the next future. Eventually “impl Trait” was introduced and then async functions, totally eliminating these difficulties.

–

I think there is no hurdle in the widespread adoption of Rust. Rust is so far ahead of its time, library authors are still experimenting and figuring out how to make libraries that are as well-designed as the language itself. Rust is steadily gaining popularity, just as we can expect of any good language, and this at a pace that library authors can keep up with. Too fast adoption of Rust and there would be pressure to stabilize libraries that still have potential for major improvements.

What I think it will take to see crazy fast widespread adoption of Rust is some killer application. Who cared about Ruby before a company that was popular in the blogosphere released Rails? The same seems to happen every time a language suddenly becomes popular. Facebook releases React, and suddenly all job listings say “React developer” instead of JS developer, as if React is the language, rather than just a little library. Maybe some day someone will follow the example of Rails and create-react-app and make a script that creates an app with all the trendiest dependencies, this time based on Rust. Then people will no longer say that Rust needs to be easier. They will just follow the instructions and make a formulaic app according to the template and the instructions in the tutorial just like everyone else.

For all we know, this could be happening right now. There could be a secret team at Google, Facebook or the next unicorn startup creating a nextgen framework for making some kind of application, this time based on Rust. Maybe tomorrow it will be in a newsfeed near you, and next year all job listings will be searching a “VuenicornX developer”, implying you have to write applications in Rust using this brand name framework. When that happens, the Rust community will never be the same again. 90% of the community could be people who use this framework, that don't care about Rust as a general purpose programming language.

I like writing. I hope someone finds this insightful.

16 Likes

I am pleasantly surprised to read multiple long answers. Thank you all for the insight.

1 Like

Note this article from O'Reilly seems to challenge the premise, suggesting the rate of adoption for Rust is actually higher in 2021 than for other mainstream languages.

4 Likes

A very good question.

About the simplest programming language I know for communicating a program to a computer hardware is the SUBLEQ assembly language.

SUBLEQ only has 1 instruction which takes 3 operands and it does the following:

subtracts the value in memory address A from the value in memory address B, storing the result into memory address B. If the result stored into memory address B is less than or equal to zero, the execution jumps to the memory address C; otherwise it continues to the next instruction.

https://esolangs.org/wiki/Subleq

How brilliant is that? In one instruction one has done some arithmetic and made a decision as to what to next. As there is only 1 instruction an opcode is not needed.

There it is, in the seconds it took to read the above you have learned the entire language!

Why doesn't the world of programmers flock to the simple to learn SUBLEQ, preferring horribly complicated Javascript or Python or whatever instead?

Clearly a high level programming language is created to convey meaning to ourselves as humans. Something that conveys our meanings more than SUBLEQ or any other machine code. Hence ALGOL, FORTRAN and all that has followed. Up to JS and Python etc.

But it gets worse. What if we have to convey meaning to many other programmers working on a huge program, on the team now or on the team years later?

I think, as crumplecup said, Zac Burns sums it up nicely: RustConf 2021 - Compile-Time Social Coordination by Zac Burns - YouTube

High level programming languages may make things easier to do and understand, a simple statement instead of hundreds of op codes. But they are very much about restricting what one can say. Think structured programming, functions instead of jumping to any old address, variables/structures instead of reading/writing any old address. Or classes and objects to restrict things even further.

Rust, for the first time introduces a further restriction, lifetimes and the borrow checker. To save those big teams on big projects from making mistakes into the future.

Or even just me trying to get anything to work reliably today :slight_smile:

5 Likes

Learners could benefit from more lifetime examples that are a little bit complicated. How lifetimes in loops work. How covariance and invariance plays out in the caller. (aka "how come when I tried to make a 'mut version' of my method and use it I can't get the lifetimes to work anymore???") What happens when you try to use a "chain of reference structs". What happens when you try to put a bunch of references into an array and then update them. Maybe an example using the httparse crate, where you give it a buffer and an array of structs and the parse method puts struct instances into the array that reference the buffer and gives back a slice that references the array. Then show what happens when you try to use that in a loop. Then show what happens when you want to return any of that from your function.

(if nothing else that httparse example should drive home that allocations and reference counting and copying and structs without non-'static references are oftentimes a reasonable approach)

One big thing I'd like is more tools to help us come to grips with the lifetimes in our own code. Like you hover over a borrowing variable and Rust Analyzer shows you its live region, its bounds, other lifetimes connected with it, and anything conflicting with it. Or inlay hints in the function headers that un-elide the lifetimes for you.

Maybe if you're getting lifetime errors calling your own functions, suggestions can include changes to your function signature if those lifetime bounds are too restrictive for what it's actually doing?

2 Likes

Of course, making Rust easier to learn would be great. The question is: what are you willing to spend?

On the one end, you could write a tutorial. If it’s not good, nobody will share it, and all you lost is your time. It’s “cheap.”

On the other end, you could redesign the language with a completely different garbage collection style, which might result in a very good programming language, but that language wouldn’t be Rust any more. That’s “expensive.”

1 Like

Before you do that, it would be wise to watch the end of this very-recent talk by Brian Cantrill to see what you would be sacrificing: Rust, Wright's Law, and the Future of Low-Latency Systems - YouTube

indeed. I meant, is rust going to be simpler compared to rust today without compromising on what makes rust, rust (the good features of rust)? I watched the talk and wondered if there are plans in progress about this.

There isn't just

  • writing tutorials on the one end
  • and sacrificing core advantages of Rust on the other end.

There might also be ways to simplify things by changing the language in a way that programmers can more easily grasp difficult concepts without sacrificing anything. Or not. But I think it's good to look out for such opportunities and not assume that any change would automatically break Rust's amazing properties.

To give an example: I wondered if pinning/unpinning really needs to be as complicated as it currently is. See this other post of me on Pin/Unpin.

1 Like

I am throwing random ideas that I found online, just as examples. I don't know if they are feasible. Obviously, there is some problem with them which is why they are not implemented.

How about removing the trailing semicolon? A lot of languages don't have a trailing semicolon. Some of the compilation errors will go away if that happens. Because all of us still forget to add semicolons.

Is it possible to add extensions in the editor to help in visualising ownership, borrowing and lifetimes? It could be in the form of lines as done in the link in the original post (rustviz) or auto-generated comments on the right side of the code.

My question is-Are there any such ideas in the discussion or under development for making rust easier?