How to Learn Rust in 2024: A Complete Beginner’s Guide to Mastering Rust Programming

So, you’re thinking about choosing Rust as your next programming language to learn. You already know what it means to write code and have some experience with at least one programming language, probably Python or JavaScript. You’ve heard about Rust here and there. People say it’s a modern systems programming language that brings safety and performance and solves problems that are hard to avoid in other programming languages (such as C or C++). Someone mentioned that Rust’s popularity is growing. You regularly see blog posts describing how Google, Microsoft, Amazon, and other big players in the field have already adopted it. Other mentions indicate that adoption in various industries is still growing. However, it’s also been whispered that Rust is not the easiest language to learn, so you worry about whether you can handle it.

If you recognize yourself in the description above, this post is for you. I aim to answer all the questions you might have about approaching Rust. I’ll first discuss why it’s beneficial for software developers to learn Rust and compare it with other competing programming languages. Then, I’ll explain which Rust features and concepts are the most important to remember. These features make Rust a significant development in the field of programming languages. With that in mind, I’ll continue with recommendations for getting started, provide you with educational resources (books, guides, tutorials, and courses), and suggest the most efficient learning paths. You might encounter some challenges while learning Rust, so I’ll offer guidance about overcoming them. Finally, I’ll cover areas of software where Rust truly shines and show how you can approach them.

Why learn Rust in 2024?

Developer surveys show that Rust is one of the top 12 programming languages heavily used in the IT industry. According to survey data, 10–13% of software developers work with Rust regularly. More importantly, if we look at those who are only learning to code, Rust’s share reaches 11–18%. Rust learners are a powerful driving force for growth. For example, The State of Developer Ecosystem Survey demonstrates that Rust has steadily grown over the last six years and doesn’t show any signs of stagnation:

Note the “Likely to adopt” column: Rust is the only language in the double digits here!

Developer appreciation for Rust is also unbeatable. How do I know that? Well, the StackOverflow Developer Survey provides amazing proof:

  • Rust continues to be the most admired language in the developer community, and has been for many years in a row (eight years? nine years? No one remembers!).
  • Those already working with Rust don’t want to switch to other programming languages.

As a regular participant in Rust developer conferences, I can testify that this appreciation for Rust is evident. Rustaceans love Rust and have created one of the friendliest communities to participate in.

On the other hand, the StackOverflow survey reports a decline in median yearly salary over the last couple of years, but this trend is also seen with other mainstream programming languages. Despite growing adoption among top and mid-size IT companies, we have yet to see Rust adopted by myriad smaller companies, which could bring more Rust vacancies to the job market and make it less competitive than it is these days (see this engaging discussion for context).

If we look at the language, features, and ecosystem, we immediately see where the numbers come from. Rust has been developed to provide memory safety and performance simultaneously. Unique choices made by the language designers ensure that you have fewer memory access errors in your code, making security breaches less likely. The price you pay for that doesn’t come with performance penalties. So, what’s the price then? Well, it’s the time you, as a developer, spend persuading the Rust compiler that your code is truly safe. Once the compiler has agreed with you, you’re safe. Memory safety and performance are extremely valuable: together, they provide a much smoother experience for those who use software solutions (which these days means everyone). Rust’s standard library, other cornerstone libraries, and tooling also enhance the developer experience. For example, developing high-performance concurrent applications in Rust is not a big deal, as the ecosystem is mature and well-equipped to support such tasks.

Developers also appreciate the wide spectrum of technology domains and industries where Rust knowledge is applicable. The most recent Rust Annual Survey identifies the following such areas:

  • Server-side and other backend applications.
  • Cloud computing applications, services, and infrastructure.
  • Distributed systems.
  • Computer networking.
  • Computer security.
  • Embedded development.
  • Game development.
  • Web frontends (including WebAssembly).
  • And many others.

Of course, Rust is not the only programming language used in these areas. There are competitors in each of them. Rust is often compared to C++ or Go because they share many areas but differ in various aspects, including memory safety guarantees, performance promises, availability of tooling and libraries, language simplicity, and industry adoption. While Rust might be lagging in the last three categories, it works on par with C++ in terms of performance and makes it much easier to avoid memory access pitfalls and build error-prone applications.

Understanding Rust’s core concepts

Let’s get a bird’s-eye view of Rust and its features. Rust is neither an object-oriented nor a functional programming language. It doesn’t feature classes and doesn’t directly support object-oriented patterns. It can operate with functions as first-class values, but this ability is limited compared to full-blown functional languages (such as Haskell or OCaml).

Writing code with functions, values, and types

Programs in Rust are structured as collections of functions combined in modules. Functions manipulate values. Values are statically typed, meaning every value or expression should have an assigned type at compile time. Rust provides both primitive and compound types (arrays and structs), and its standard library also provides many additional types for collections of values. Rust supports generic types, which makes it possible to avoid mentioning specific types and provide more general definitions. Rust also provides traits as collections of methods (i.e. functions) that can be implemented for specific types. Traits allow Rust to achieve the same levels of software abstraction as object-oriented languages that support inheritance and polymorphism.

Memory management

Rust’s approach to memory management is based on the following principle: the Rust compiler must know the precise locations in the code where memory is allocated, where and how it’s accessed, and where it’s no longer needed. This knowledge allows for controlling memory access and freeing allocated memory automatically by inserting the corresponding instructions directly into the generated code, thus avoiding many common pitfalls other languages might be susceptible to. This approach differs from automatic memory management (as in JavaScript, Python, Java, or C#), where memory fragments that are no longer needed are detected at runtime and garbage is collected. As a result, Rust saves the time required to execute the corresponding algorithms at runtime and achieves both memory safety and performance.

To be able to infer knowledge about memory access, Rust sets limits on what can be done with memory and defines strict rules that ensure correctness:

  • Every memory fragment must be owned by a single variable – Rust’s ownership model is based on this.
  • Mutating a memory fragment requires exclusive access (as opposed to just reading the memory).
  • Rust allows creating mutable and immutable references to memory fragments (borrowing them) but uses a borrow checker to enforce correctness (for example, prohibiting more than one mutable reference).
  • The Rust compiler computes and checks lifetimes for every variable in a program from the place it’s created to the place it’s dropped (where it becomes no longer accessible).

The compiler’s requirements can be too strict. This can be a common frustration, especially when learning the language: The Rust compiler may fail to accept a logically correct code fragment.

To make these concepts a little easier to catch, let’s discuss Rust’s equivalent of the following Python program:

def print_list(numbers):
   for number in numbers:
       print(str(number) + " ", end="")
   print()


def add_one(numbers):
   numbers.append(1)


def main():
   numbers = [1, 1, 1]
   print_list(numbers)
   add_one(numbers)
   print_list(numbers)


if __name__ == '__main__':
   main()

So, we have a memory fragment (Python’s list) with three elements. We print it, add one more element, and print it again. We never mention any type here. Python doesn’t need any sign of memory management in the program, although the memory must be allocated and freed at some point. Moreover, we pass around the numbers list easily without thinking about memory access control. 

The same code in Rust is different, not only in syntax but in the whole approach to types and memory management:

// Here we take a vector by reference (&).
// We are not allowed to mutate elements.
// We don't take ownership; we just borrow.
fn print_vec(numbers: &Vec<i32>) {
   for number in numbers {
       print!("{} ", number);
   }
   println!()
}


// Here we take a vector by mutable reference (&mut).
// We are now allowed to mutate elements and the vector itself.
// We still don't take ownership; we just borrow.
fn add_one(numbers: &mut Vec<i32>) {
   numbers.push(1)
}


fn main() {
   let mut numbers = vec![1,1,1];
   // We pass a reference
   print_vec(&numbers);
   // We pass a mutable reference
   add_one(&mut numbers);
   // We pass a reference again
   print_vec(&numbers);
}

It’s instructive to explore these two code fragments and find similarities and differences between them by yourself. Even without understanding Rust, you might get a general feeling for it just by looking at this code.

Despite passing references around, the numbers variable still owns the allocated memory. Rust defaults to read-only memory access, requiring explicit specification for write access. Rust also ensures the memory is freed after the last usage in the second call to print_vec

Here’s a variation of the add_one function that takes over ownership of a vector and renders the whole program incorrect:

fn add_one_incorrect(mut numbers: Vec<i32>) {
   numbers.push(1)
}

What’s the problem? Well, after calling the add_one_incorrect function, the numbers variable no longer owns the memory; its lifetime has ended, so we can’t print anything! The screenshot below shows that we’re in trouble:

Before introducing the incorrect version, Rust’s borrow checker could ensure that passing references didn’t bring any problems. After introducing an error, it’s no longer feasible. The language’s behavior here exemplifies how memory access is under strict control.

Concurrency

Concurrency is the ability of a system to execute multiple tasks or processes simultaneously or in overlapping periods to improve efficiency and performance. This can involve parallel execution on multiple processors or interleaved execution on a single processor, allowing a system to handle multiple operations simultaneously and manage multiple tasks more effectively.

Rustaceans often describe Rust’s concurrency as fearless. Several factors contribute to this perception:

  • The ownership model adds more control over sharing data between concurrent threads.
  • Embracing immutable data structures simplifies the implementation of concurrent algorithms and contributes to thread safety.
  • Message passing via channels adopted in Rust dramatically reduces the complexities of shared-state concurrency.
  • Rust’s approach to lifetimes and memory management generally makes code that works with concurrency primitives such as locks, semaphores, or barriers more elegant and safe.
  • In many cases, Rust’s approach to asynchronous programming makes it possible to avoid using complex concurrency patterns and enables you to write concise and clear code.

Although concurrency is not the first thing beginners learn when approaching Rust, it is still easier to grasp than in many other programming languages. More importantly, Rust helps you write less error-prone concurrent code.

How to get started with Rust

There are only two things that need to be installed on your computer to get started with Rust:

  1. Rust toolchain – a collection of tools that includes the Rust compiler and other utilities.
  2. A code editor or IDE (integrated development environment). 

The first step is easy – just go to rustup.rs and run a command they suggest or download an installer, depending on your operating system.

The second step requires making a choice. Your options include using a code editor configured to provide Rust support (for example, Visual Studio Code supports Rust but requires some setup) or using a dedicated Rust IDE (such as RustRover, a JetBrains IDE, which is free for anyone learning Rust).

If you choose RustRover, you don’t need to configure anything. You can launch it, create a new project (using the New Project button on the welcome screen), and run the Hello, world! program generated by default (use the green triangle icon in the gutter to run the main function):

If you encounter any trouble, refer to RustRover’s Quick Start Guide

Anything beyond really simple stuff requires adding external dependencies. Rust projects rely on Cargo, a package manager. Rust packages are called crates. The dependencies are specified in the Cargo.toml file, for example:

[package]
name = "hello"
version = "0.1.0"
edition = "2021"

[dependencies]
chrono = "0.4.38"

After adding the chrono crate to our dependencies, we can use the functionality it provides in our code, for example:

use chrono::{DateTime, Local};

fn main() {
   println!("Hello, world!");
   let local: DateTime<Local> = Local::now();
   println!("Today is {}", local.format("%A"));
}

In this program, we get the current (local) date and time, format it as a day of the week, and then print a result. For example, running this program gives me:

Hello, world!
Today is Thursday

Learning basic syntax, data types, and control structures (if-conditionals, loops, etc.) in Rust is a parallel process to learning about the available crates and what they provide. Once you can run Rust programs, you’ll be ready to work with external dependencies. Let’s talk about the resources that simplify learning Rust.

Learning path and resources

If you’ve ever learned a foreign language, you know that various skills are required, such as reading, writing, listening, and speaking. There are also different types of activities to develop these skills. Teachers always encourage you to read texts, watch videos, listen to podcasts, speak whenever possible, post on social media, etc. Learning programming languages is no different. When learning how to code in Rust (or any other programming language), you:

  • Read books, documentation, blog posts, release announcements, and social media discussions (which is all vital to staying informed and in context).
  • Watch videos from tech video bloggers.
  • Explore code samples (which necessarily includes reading and modifying them!).
  • Write code to complete exercises.
  • Follow hands-on tutorials.
  • Develop simple projects from scratch (this is extremely important – project-based learning is always helpful for developing independent coding skills).
  • Maybe even contribute to open-source projects (there’s always something easy enough for beginners – just ask!).

Note that there should be a balance between reading about programming and writing code. It’s impossible to master a programming language without actually writing code. At the same time, avoiding books or other extended materials can lead to a relatively shallow understanding of the internal details; any complex case could become extremely hard to resolve without external help (which is fine but can slow you down on your path to becoming a professional developer).

When you write code, it’s important to both replicate externally crafted exercises and follow your own (or borrowed) ideas for developing simple applications. Ensure that your exercises are not limited to grasping algorithms but also teach language features and library functionalities. When developing simple applications, you learn to choose a language feature or a library that best suits your goal. This is an essential skill in itself.

So, the key to successful learning is combining all the approaches. Here I have a collection of resources that can help you with that.

Rust books

There are many good books available for learning Rust:

  • The Rust Programming Language is an official, regularly updated book for those learning Rust. It is known as The Book within the community. I also recommend another version of this book that adds more interactivity, such as quizzes, visualizations, and exercises. This version was developed by Brown University researchers Will Crichton, Gavin Gray, and Shriram Krishnamurthi. There’s also an interview with Will Crichton, who discusses this experiment to enhance Rust learners’ experience.
  • Rust in Action by Tim McNamara (mostly known as timClicks – my advice is to follow him everywhere).
  • Hands-on Rust by Herbert Wolverson – the book that is especially appealing to those who appreciate project-based learning (you can also find a lot of free videos by Herbert about various Rust concepts; I can’t recommend them enough).
  • Rust for Rustaceans by Jon Gjengset, a Rust book that many community members highly praise.  
  • Code Like a Pro in Rust by Brenden Matthews.
  • Learn Rust in a Month of Lunches by David MacLeod.
  • After a few months of learning Rust, you should check out Mara Bos’s Rust Atomics and Locks. It’s a masterpiece in explaining the low-level concepts behind Rust concurrency.

Rust tutorials

For those who prefer more hands-on approach to learning, there are also tutorials and courses with exercises:

Rust on YouTube

For Rustaceans who like learning on YouTube, I recommend following several channels there:

  • Let’s Get Rusty – I don’t know how it works, but it’s hard to stop watching these videos.
  • Jeremy Chone is great at explaining complex concepts.
  • Chris Biscardi talks about game development in Rust with the Bevy framework, among other things. Creating a game is an excellent project for anyone!
  • Jon Gjengset – For the first couple of years of your Rust journey, it can be hard to follow his in-depth technical streams, but I recommend watching them early! 

Rust community

Of course, many more learning resources are available, but it’s impossible to list all of them. The good news is that you never stop learning Rust because the language is constantly evolving.

Common challenges and how to overcome them

Learning Rust can be challenging. However, you can overcome these difficulties and master the language with the right strategies. Here are some tips to help you along the way:

  1. Understand the ownership model. Rust’s ownership model, which includes concepts like ownership, borrowing, and lifetimes, is often the most challenging aspect for beginners. Start with small examples and practice frequently. Focus on understanding why Rust enforces these rules rather than how to get around compiler errors. 
  2. Take your time with the borrow checker. The borrow checker can be frustrating, as it’s strict and may lead to confusing compiler errors. When you encounter borrow checker issues, take a step back and analyze what Rust is trying to prevent. Experiment with approaches like referencing or cloning data (but try not to clone everything!). Don’t be afraid to ask for help on forums like Reddit or the Rust Community Discord server. It’s really useful to understand what precisely a borrow checker is. See, for example, this great explanation by Nell Shamrell-Harrington.
  3. Leverage Rust’s excellent documentation. Rust is a relatively new language, so some things might not be as straightforward as in more established languages. Use the Rust documentation, which is known for being comprehensive and well-written. The Rust community also contributes to great resources like the Rust by Example guide.
  4. Start with simple projects. Rust’s complexity can make even simple projects feel overwhelming at first. Start by implementing basic programs and gradually move to more complex ones. Examples of simple projects include building a command-line tool with clap, writing simple file parsers, or creating a basic web server using a framework like Rocket.
  5. Learn your code editor or IDE. Writing Rust code manually can be error-prone. Tools can help you with code completion, linting, and formatting.
  6. Participate in the Rust community. Learning a new language can feel isolating. Engage with the Rust community by contributing to open-source projects, joining Rust-focused chat rooms, or participating in forums. Rust’s community is known for being very welcoming and helpful to newcomers.
  7. Learn from mistakes. Rust’s compiler is strict, and it can feel like you’re constantly fighting with it. View each compiler error as a learning opportunity. Rust’s error messages are often very descriptive and guide you toward the correct solution. Over time, you’ll find that these errors help you write better, safer code.
  8. Be patient. It’s easy to get discouraged with Rust. Remember that Rust is designed to be safe and efficient, which requires you to learn some complex concepts. Take breaks when necessary, and don’t be afraid to revisit topics multiple times until you’re confident that you’ve fully grasped them.
  9. Join Rust learning groups. Many developers are learning Rust, and joining a group can provide support and motivation. Look for local meetups or online study groups, or even partner with a friend to learn together.

Mastering Rust takes time, but the payoff is worth it. Rust’s features, like its memory safety guarantees and performance, make it a powerful language for systems programming, web development, and more. Stick with it; you’ll find that Rust can be fun and rewarding.

Building real-world applications with Rust 

Let’s now briefly examine Rust’s suitability for building real-world applications. We’ll focus on several relevant technology domains, as case studies in these areas highlight Rust’s strengths and demonstrate its practical applicability.

Rust in web development

The Rust ecosystem is perfectly suitable for backend web development. Many frameworks and libraries perform general web development tasks, such as HTTP request routing, JSON and form handling, templating, database access, authentication and authorization, logging and tracing, etc., with production-ready performance. I recommend looking at the AreWeWebYet? website for details and useful links. As a spoiler, the answer is positive. 

Most web frameworks for Rust are easy to start with, and their documentation is awesome, partly thanks to the competition between them. For example, the Actix Web community provides a nice and up-to-date collection of examples. My colleague Khalid Abuhakmeh wrote a tutorial for beginners on building a simple HTML web application that uses Rocket, another web framework for Rust, as a backend. Luca Palmieri, whom I mentioned earlier, authored a book, Zero To Production in Rust, that helps you become a web backend developer, and he also works on Pavex, an emerging and promising web framework. One exciting thing about the latter project is that Luca carefully documents design choices in a series of progress reports. It’s constantly engaging to see why things are designed the way they are and not in some other way. Check out this web framework comparison to understand how actively Rust web frameworks are developing.

Rust is also active on the web frontend, though it may not be 100% ready for production. Still, the overall progress in Rust for WebAssembly and frameworks such as egui or Dioxus is impressive.

Integrating Rust with other programming languages

I wonder if Rust designers envisioned Rust becoming a language of choice for those who develop tooling and libraries for other programming languages, especially Python and JavaScript. Rust can provide better performance, and it’s attractive enough for developers from other language ecosystems to learn and apply. 

The PyO3 project enables developers to implement native Python modules in Rust and to access Python in Rust binaries. By leveraging PyO3, you can provide an extremely efficient Rust implementation of a Python library for Python developers. Rust and JavaScript can be connected similarly via WebAssembly and wasm-bindgen

Astral and its founder, Charlie Marsh, prove that it’s possible to develop Python tooling that works at the speed of light (well, we all know that that’s a marketing claim, of course, but the result is truly fascinating). 

Deno, a JavaScript runtime developed in Rust, delivers very good performance. This is not the only example from the JavaScript ecosystem. There’s even a curated collection of JavaScript tooling implemented in Rust. 

Implementing interoperability between Rust and other programming languages is also possible.

Systems and embedded programming in Rust

Undoubtedly, Rust shines in systems programming. Speakers from Google and Microsoft confirm that introducing Rust into their codebases lessens the number of security vulnerabilities, increases performance, and also keeps or sometimes improves the productivity of software developer teams. Amazon not only supports Rust development on AWS but also uses it directly to implement their own infrastructure. Cloudflare, a company that builds content delivery networks and other networking infrastructure, relies on Rust in a lot of low-level projects and contributes their frameworks to the Rust ecosystem. Ferrous Systems, a company that backs the development of rust-analyzer, also develops Ferrocene, a certified Rust toolchain for mission-critical applications. Together with other developments, this enables Rust to be used in the automotive and aerospace industries. For example, there are reports about Volvo and Renault using Rust for their in-vehicle software.

Conclusion and next steps

It’s a great time to start your Rust journey. Rust is a good industrial-level language with a wealth of learning materials and a beginner-friendly community. While there are some challenges, given the unique core concepts, there are also ways to overcome them. Once you are there, the job market and various application domains are open. 

I hope that the learning path and resources I’ve presented will help you on your way. I can only recommend setting a learning goal beforehand: find an open-source project you want to contribute to or develop an idea for your project early. You’ll be amazed how quickly you can get close to fulfilling that goal.

Be encouraged and stay on the path of learning Rust!

image description