Top 5 Reasons Why You Should Learn Rust

Jayson Lennon
Jayson Lennon

Should I Learn Rust?

Being a Rust programmer myself and someone who teaches Rust to others, I might be a little biased but this is a question I get asked a lot. So I'm finally putting all of my thoughts in one condensed post.

By the end of this post, I think you'll see why myself and thousands of other developers think Rust is the best it gets when it comes to programming languages.

Here's the quick version:

Software complexity gradually increases over time: more features are demanded in a shorter amount of time, and as standards go up, tolerance for bugs diminishes. New programming languages and libraries emerge and sometimes make marginal improvements, but these mostly just maintain the status quo.

So, what is Rust then?

Rust is the super productive, super safe, and super fast language that breaks the trend of marginal improvements.

Not only that, it takes one important step further: it changes the way you think about software development.

Some may say (Andrei is definitely one of them) that Rust is the perfect programming language love-child that combines the power of C++ with the safety of Java or other interpreted languages.

These are the 5 top reasons why you should learn Rust:

  1. Puts Developers First
  2. Dependable Code
  3. WebAssembly (Wasm)
  4. Industry Support
  5. Proven Track Record

I'm going to walk you though each each of them.

Before we jump in, just 2 housekeeping items:

  1. If you're wondering who the heck I am... well, I'm Jayson. Nice to meet you. If you really want, you can read a bit more about me here.
  2. We do get a bit technical and in the weeds with this post. I assume you already have some basic programing knowledge. If you're brand new to programming, we're so glad you're here and don't be scared or intimidated. If things are going way over your head or you have no idea where to start, I'd recommend taking the Tech Career Path Quiz as a first step.

Ok, pressure's on, let's convince you why you should learn Rust!

1) Puts Developers First

Rust puts developers first by providing features that make software development smooth and reliable. This includes maintenance, which is a large part of a project's lifecycle.

Here are the reasons why Rust is the most loved programming language six years in a row on the yearly Stack Overflow developer survey, and why it's my go-to language when starting new projects.

Rust is the most loved language

Ecosystem & Tooling

The Rust ecosystem is what really drives the Rust development experience. There are tens of thousands of crates (a crate is a Rust code library) available on crates.io, all complete with auto-generated documentation.

These crates can be accessed using the cargo tool, which comes as part of the Rust toolchain. cargo is your universal build tool that manages code dependencies, generates documentation, tests and lints your code, runs benchmarks, and compiles your program. It's also extensible: so if you need new functionality you can write an extension, such as managing code licenses, checking for unsafe code, running fuzz tests, or checking for known security vulnerabilities.

Reliable code is more than just code that runs properly. It has to be open for refactoring, easy to read by other developers (including your future self!), and consistent. To make this a reality, Rust comes with additional tools: clippy, rustfmt, and rustdoc.

Linting with Clippy

Clippy is the Rust code linter that turns the compiler assistance up to 11. It can catch performance-related problems, misuse of specific functionality, and enforce Rust coding conventions.

It goes almost too far (or not far enough?) by actually re-writing your code as part of its output, so you can just copy it to your project. Clippy is fully configurable, so you can enable and disable lints on a case-by-case basis, directly in your code, by applying specific rules at the function level.

You can change lint severity as well: want a specific lint to always fail your CI pipeline? Just change it from warn to deny and now you'll always be aware when an issue crops up.

Code Formatting with Rustfmt

The value of consistent code cannot be understated. Rust has a specific coding style that can be automated across an entire codebase using the rustfmt tool. This tool has widespread IDE integration, so it's usually automatically enabled when configuring your IDE for Rust.

This makes it easy to jump into an existing codebase and focus on the problem, instead of worrying about following specific style rules. Similar to clippy, the rustfmt tool can be disabled at the function level in cases where you need fine-grained control over how your code looks.

Consistent Documentation with rustdoc

Finally, we have rustdoc. This tool generates documentation for your project and outputs a browsable web page that allows you to search for function names, return values, and data types. Just like rustfmt, the rustdoc tool has widespread usage across the Rust ecosystem, making it easy to get information on a project.

Documentation in Rust is more than just a few HOW-TO lines; it has full Markdown support, and the compiler will actually test example code snippets that are present in your documentation. This ensures that developers who are first using your project will have up-to-date and accurate examples to build upon. Documentation for every published Rust crate is available online at docs.rs.

Vibrant Community

The Rust community is very welcoming to all programmers and has a strict Code of conduct which ensures that Rust communities are friendly and comfortable. You will always feel at home in the Rust Discord channel and on the developer forums.

If you are new to Rust and want to contribute to Rust projects, many repositories use the good first issue or easy tags on their issues, so you can jump right into the code.

Community resources are also available in the form of the Official Rust Book, and Rust by Example, which walks you through the language using, you guessed it, lots of example code!

2) Dependable Code

Null pointer errors? Nah.

Runtime problems cropping up 6 months later? Nope.

Waking up at 2AM for an on-call emergency hotfix? Not with Rust.

Reliability is the bread-and-butter of Rust programs and is the reason why Rust is the language of the future. Let's take a look at how Rust is able to provide extreme reliability.

The Compiler

Compilers aren't something that normally comes to mind when working on a project. It's just the thing that builds your program and sometimes emits cryptic error messages for pasting into a Google search.

Rust is different.

The Rust compiler is your personal code assistant. It checks your work, provides detailed help messages, re-writes your erroneous code with code it thinks might work properly, and once it's satisfied, produces output that runs correctly, efficiently, and reliably. Here are a few examples of the fantastic help provided by the compiler:

Number out of range

Immutable reassignment error

Not satisfied with these help messages? Rust also has the --explain command which provides an example of broken code, explains why it's broken, and provides the steps on how to fix it. This is great when running into new problems where you aren't able to figure out the issue from just the brief error messages shown in the screenshots.

Editions: Long-Term Stability

The Rust compiler ships a new version every six weeks. About every three years, a special version called an edition is shipped.

Editions are Rust's way of introducing large, potentially breaking changes, without impacting the Rust ecosystem. Once an edition ships, all new Rust projects you create will default to the new edition, and each edition is guaranteed to build & run as long as Rust exists.

With editions, that old code you wrote 6 years ago will still build and run using the newest Rust compiler, even if there have been breaking changes during that time period. You can think of editions as a "front-end" layer to the Rust compiler: mostly syntactic updates which eventually get translated into the "back-end" compiler code. This means your old code can potentially receive performance improvements just by rebuilding it with the newest version of the compiler.

Crater

The cargo tool and Rust ecosystem are thoughtfully implemented and extremely robust. Something that doesn't get much attention, but plays a critical role in Rust's stability (and its ability to be released every 6 weeks), is a tool called crater.

Crater is a tool that is run before new Rust versions are released that builds the entire crates.io registry (over 65k crates as of this writing), runs the test suites for all the discovered codebases, and generates a report which allows the Rust team to determine if any upcoming changes will break part of the ecosystem.

Thanks to Crater, the compiler has been tested against tens of thousands of codebases before you even have the chance to get the newest version. This means you can try out all the hot new language features every 6 weeks without worrying about Rust breaking your project and/or eating your code!

Handling Branches with Pattern Matching

No, I'm not talking about your awesome git feature branch. I mean whether code at line 80 gets executed or code at line 83 gets executed.

It's usually a chore to write enough test cases to cover all possible code branches. Rust alleviates this by implementing exhaustive pattern matching, which checks to make sure your code has an execution path for specific types of input. This is enabled using the match keyword, which requires you to provide behavior for every possible value of a data type:

// Type annotation not needed, will be inferred by the compiler.
let my_value = true;

// Use 'match' to check for each possibility of the boolean value.
match my_value {
    true => println!("value is true"),
    false => println!("value is false")
}

With match, errors that would normally only be caught at runtime in other languages, become compile-time errors in Rust. Take, for example, the same code as above, but without the false branch:

let my_value = true;
match my_value {
    true => println!("value is true")
}

This emits the following helpful compiler error:

Non-exhaustive error

Pattern matching seems like a small feature when you consider Rust as a whole, but this simple keyword enables your application to run extremely reliably since there will always be some code to execute, regardless of the input data. This provides confidence in the stability of your code and lets you focus on domain solutions instead of worrying about language gotchas.

Expressing Data with Enumerations

Pattern matching isn't just limited to simple things like a boolean. It can be applied to data structures you create yourself. Take, for example, a hotel booking system for a large hotel chain. Booking a room can become complicated, but Rust provides enumerations to express this in a concise way, which lets you focus on the problem at hand:

/// Possible outcomes of booking a room.
enum Booking {
    Booked(ConfirmationNumber),
    Unavailable,
}
/// A function to book a room at a hotel.
fn book(hotel: &Hotel, room: &Room) -> Booking {
    // ...
}

Data can be encapsulated within enumerations, so we are able to express multiple different outcomes along with any data associated with that outcome.

Considering that the compiler will emit errors if we fail to check for a possible outcome, we can modify our existing enumeration as such:

enum Booking {
    Booked(ConfirmationNumber),
+   NoVacancies,
    Unavailable,
}

Compiling the program will now provide a list of all code utilizing hotel bookings which needs to be updated to account for the new outcome. This saves time, reduces bugs, and ensures your application will run as expected whenever changes are made.

If we apply this to an entire codebase, you get extremely reliable programs that are very easy to change. Want to refactor some code? No problem! Just make some changes, follow compiler errors until the program builds, then re-run your test suite. After refactoring, the odds of your test suite not passing are extremely low.

When Things Go Wrong

There is always the possibility of failure, and Rust takes a different approach than many programming languages. There are no exceptions in Rust, because Rust likes things to be obvious, and exceptions being thrown from anywhere in the code is definitely non-obvious.

Instead, Rust has the Result data enumeration. Result encapsulates either a successful value, or an error value. Reading a function signature and seeing a Result as the return value is concise, and makes it clear that the function might fail:

/// Possible errors when making a connection.
enum ConnectionError {
    InvalidPassword,
    NoResponse,
    Timeout,
}
/// Make a new connection, but it might fail with a ConnectionError.
fn connect(ip: Ipv4Addr) -> Result<Connection, ConnectionError> {
    // ...
}

This is super efficient, self-documenting (there's no hidden case where an exception gets thrown), and allows errors to be handled using all the familiar functionality that is available for working with any other kind of data.

Combining match with this function enables us to easily access both success and error values:

let address = Ipv4Addr::new(127, 0, 0, 1);
match connect(address) {
    // Successful case
    Ok(connection) => /* alright we are connected! */,
    // Error case. Let's match on the Error and find out what happened!
    Err(e) => match e {
        ConnectionError::InvalidPassword => /* whoops! */,
        ConnectionError::NoResponse => /* wrong address maybe? */,
        ConnectionError::Timeout => /* might be a busy server */,
    }
}

Of course, this level of detail is only useful sometimes. Remember that Rust puts you first, so if you only want a successful value, you can get it using the question mark operator:

let connection = connect(Ipv4Addr::new(127, 0, 0, 1))?;
// ...
// send some data across the connection, etc
}

Any errors that happen in the connect function will now get returned automatically, and you can focus on implementing your ideas.

Fixing the Billion Dollar Mistake

Null in programming is simply the absence of a value; usually something that has not yet been initialized. While this seems simple enough, programming in a language that allows nullable types (also known as the billion dollar mistake by its inventor) is a lot like buying a used car. Hopefully the car runs, but you can never be sure without inspecting it fully. It can even run just fine during a test drive, but there may be something missing that eventually leads to problems later.

Let's see this in action with my second favorite language, Python:

class Element(object):
    def log(self):
        print("logged!")

def take_action(elements):
    for el in elements:
        el.log()
# Runtime Error: Null/None is not iterable
take_action(None)
# Runtime Error: List of None is iterable, but no log function
take_action([None])
# Runtime Error: first element works, but second element causes a crash
take_action([Element(), None])
# Runtime Error: numbers are not iterable
take_action(3)
# Runtime Error: strings are iterable, but no log function on characters
take_action("hello")
# Works: Empty list is iterable, but the list is not actually iterated
take_action([])

If we run this Python code, we will get runtime errors for all but the last take_action function call.

This issue is only marginally improved using a statically-typed language like C# or Java -- null is still present in those languages, so the first three take_action function calls would still be runtime errors in those languages.

This is a simple example, but in even a small to medium-sized codebase, it is not always apparent if you are working with null data returned from a function, instead of the data that you actually want. The code must be manually traced in order to determine if a null might be returned and this takes time and is prone to mistakes. The end result is null pointer exception errors occurring periodically when uncommon data passes through your program, which leads to the persistent need for bug fixes.

Here is the same program written in Rust:

struct Element;
// Implement some functionality for Element.
impl Element {
    fn log(&self) {
        println!("logged!");
    }
}
// Vec is short for Vector, which is just a dynamic array/list.
fn take_action(elements: Vec<Element>) {
    for el in elements {
        el.log();
    }
}
fn main() {
    // Compiler Error: None is not a Vector
    take_action(None);
    // Compiler Error: Need a Vector of Element
    take_action(vec![None]);
    // Compiler Error: Number is not a Vector
    take_action(3);
    // Compiler Error: String is not a Vector
    take_action("hello");
    // Compiler Error: Cannot mix multiple types in a Vector
    take_action(vec![Element, None]);
    // Works: Vector is the type we need
    take_action(vec![]);
}

In contrast to the Python code, we won't even be able to compile this Rust code, let alone run it. We will get 5 compiler errors that look something like this:

Wrong type error

This is the power of having a robust type system that banishes null: errors that typically would occur at runtime now occur whenever you compile your program. This saves a huge amount of developer time since the bugs that were present in the Python program are now hard errors in Rust. Also, when you consider that maintenance is nearly 70% of a project's total cost, moving these errors to compile time has large economic benefits over the life of a project.

Choose Your Level of Detail

match is great, but not all situations require checking every possible outcome. We already saw the question mark operator, and Rust provides many other tools to express solutions in the most readable manner possible. So if you don't need to check every outcome, you don't have to. You can check only a few specific cases, or even just check if something worked or not:

let connection = connect(Ipv4Addr::new(127, 0, 0, 1));
if connection.is_ok() {
    // Alright, we are connected!
} else {
    // Something went wrong, let's report a generic error.
}

This applies to writing functions as well. Sometimes it isn't necessary to be super-specific about what data you are working with. When this is the case, you can go dynamic and forget about the details:

// We are using a dynamic Error, which lets us return any kind of error.
fn call_api(conn: &Connection) -> Result<ApiData, Box<dyn Error>> {
    // ...
}

The compiler is still able to keep a close eye on your code when working with dynamic data, so if you make a mistake, Rust is there to help you out, just as if you had defined all of the details for a specific data type.

3) WebAssembly (Wasm)

The future of applications are those that are hosted on the web and accessed via your favorite browser. The reasons for this are compelling and undeniable: true multiplatform support, secure network connections, beautifully styled UI, and the largest userbase of any computing platform. The web application space is only growing and with no end in sight.

If you haven't yet heard of it, Wasm is a binary format that runs in web browsers. It enables amazing things such as running Doom3 or AutoCAD in your web browser at near-native speeds (Doom3 on your phone is pretty great, but only if you have a bluetooth controller 😉).

Where does Rust fit into this? Well, Rust is positioned to be the de-facto language when targeting Wasm on the web. The memory usage of Rust is minimal, there is no garbage collector, and Rust has Tier 2 compiler support for targeting Wasm, along with a fantastic Wasm packager that can be used with web development workflows like webpack. This allows Rust to take full advantage of Wasm in order to deliver large speedups on computationally expensive web applications.

Since Rust can run both on the backend (natively) and frontend (via Wasm) of web applications, learning Rust becomes a clear choice for creating performant and reliable web applications that can be accessed anywhere, and on any device.

4) Industry Support

The future of Rust is bright. The Rust Foundation manages the project and ensures that core contributors are fully supported in order to keep the Rust project active and in constant development.

Microsoft is a founding member, along with Amazon, Google, Huawei, Facebook, and of course, Mozilla, who helped initially create the Rust language.

5) Proven Track Record

Rust has already been integrated into large projects and is either running in production or being prepped for production, right now. You probably haven't heard many Rust success stories, and that's likely because Rust's interoperability allow it to slowly chip away at existing codebases. Improving existing projects usually doesn't get the same fanfare as a shiny new product, so here is a small sample of some of the awesome things happening with Rust:

  • Support for Rust Linux kernel drivers was recently added to linux-next, which is the staging area for inclusion into the release kernel. The Linux project has only ever accepted C code, so this is a big deal.
  • Cloudflare transitioned their application firewall to Rust, which is responsible for protecting 25 million websites.
  • npm (the JavaScript package manager with 1.3 billion downloads per day) uses Rust for their registry operations.
  • Dropbox rewrote their sync engine in Rust to deal with their scaling problems.
  • Discord augmented their codebase with Rust for faster Member List processing (great news for all of us in the ZTM server!)
  • Google is writing new Android components in Rust to increase the security of billions of devices.

There are many more cases of smaller companies using Rust in production, and the list is only growing.

Wrapping Up

Given that Rust is very popular, has a vibrant community, has industry backing, and generates super fast and super efficient programs, now is the perfect time to get in early and learn Rust!

And if I've convinced you to start learning Rust, check out my brand new course, Rust Programming: The Complete Developer's Guide where I teach you how to code & build real-world applications using Rust. No previous experience needed.

You can also try some of the lessons for free, just click any lessons with a preview button.

More from Zero To Mastery

Learn to code for free, get hired in 5 months, and have fun along the way [Full Guide + PDF] preview
Popular
Learn to code for free, get hired in 5 months, and have fun along the way [Full Guide + PDF]

In 2015, I taught myself how to code and got hired in 5 months. This is the step-by-step guide I created for myself (which I update every year). Over the years, 1,000s of people have used it to learn to code for free & get hired as a web developer.

The 6 Mistakes I Made When Learning To Code (And How To Get Past Them) preview
Popular
The 6 Mistakes I Made When Learning To Code (And How To Get Past Them)

This article will help you save time and avoid the mistakes that a lot of beginners make. If I could go back in time, this is how I would have saved myself from countless hours, days, and months working on the wrong things.

7 Ways to Earn a Side Income as a Developer preview
Popular
7 Ways to Earn a Side Income as a Developer

7 different ways in which you can leverage your web development skillset to earn an extra income on the side