According to the StackOverflow surveys, Rust has been the most-loved programming language for the last four years in a row. Most of the people that have tried out Rust would like to continue using it.
But if you haven’t used it, you might wonder — what is Rust, why is it so special, and what makes it so popular among developers?
In this guide, I’ll try to give a quick intro and answer all the questions you might have about Rust.
What is Rust?
Rust is a low-level, statically-typed multi-paradigm programming language that’s focused on safety and performance.
Rust solves problems that C/C++ has been struggling with for a long time, such as memory errors and building concurrent programs.
It has three main benefits:
better memory safety, due to the compiler;
easier concurrency due to the data ownership model that prevents data races; and
zero-cost abstractions.
Let’s go through each of these in turn.
No segfaults
If you want to do system programming, you need the low-level control that memory management provides. Unfortunately, manual management comes with a lot of issues in languages like C. Despite the presence of tools like Valgrind, catching memory management problems is tricky.
Rust prevents these issues. Rust’s ownership system analyses the program’s memory management at compile-time, making sure that bugs due to poor memory management can’t happen and that garbage collection is unnecessary.
Furthermore, if you want to do super-optimized implementations in a C-like manner, you can do that while expressly separating them from the rest of the code with the unsafe keyword.
Easier concurrency
Due to the borrow checker, Rust can prevent data races at compile-time.
Data races occur when two threads access the same memory at the same time, and they can lead to some nasty, unpredictable behavior. Thankfully, preventing undefined behavior is all that Rust is about.
Zero-cost abstractions
Zero-cost abstractions make sure that there is virtually no runtime overhead for the abstractions that you use. In simpler words: There is no speed difference between the low-level code and one written with abstractions.
Are these things important? Yes. For example, around 70 % of the issues addressed by Microsoft in the past 12 years have been memory errors. Same with Google Chrome.
What is Rust Good For?
Rust is a rather low-level language; it’s useful when you need to squeeze more out of the resources you have. Since it’s statically typed, the type system helps you deter certain classes of bugs during compilation. Therefore, you will tend to use it when your resources are limited, and when it is important that your software doesn’t fail. In contrast, high-level, dynamically-typed languages like Python and JavaScript are better for things like quick prototypes.
Here are some of Rust’s use cases:
Powerful, cross-platform command-line tools.
Distributed online services.
Embedded devices.
Anywhere else you would need systems programming, like browser engines and, perhaps, Linux kernel.
For example, here are a few operating systems, written in Rust: Redox, intermezzOS, QuiltOS, Rux, Tock.
Is Rust object-oriented?
Who knows what object-oriented means nowadays?
The answer is not really. Rust has some object-oriented features: you can create structs, and they can contain both data and associated methods on that data, which is kind of similar to classes minus inheritance. But in contrast to languages like Java, Rust doesn’t have inheritance and uses traits to achieve polymorphism instead.
Is Rust a functional programming language?
Even though Rust is superficially quite similar to C, it is heavily influenced by the ML family of languages. (This family includes languages like OCaml, F#, and Haskell.) For example, Rust traits are basically Haskell’s type classes, and Rust has very powerful pattern-matching capabilities.
Rust does feature more mutability than functional programmers would usually be accustomed to. We can think of it like this: both Rust and FP try to avoid shared mutable states. While FP is focused on avoiding the mutable state, Rust tries to avoid the shared part of the danger. Rust is also missing a lot of stuff that would make functional programming doable in it, such as tail call optimization and good support for functional data structures.
All in all, there is enough support for functional programming in Rust for somebody to have written a book about it.
Is Rust good for game development?
Theoretically, yes. Since Rust is focused on performance and does not use a garbage collector, games written in it should be performant and predictably fast.
Unfortunately, the ecosystem is still young, and there is nothing written in Rust that would compare to Unreal Engine, for example. The pieces are there, though, and Rust has a lively community. If you want to see examples of games written in Rust, you can go to the Rust game dev subreddit.
More on Rust game dev: Are we game yet?
Is Rust good for web development?
Rust has multiple frameworks for web development, like Actix Web and Rocket, which are very usable and well-built. In particular, if you are looking for pure speed, Actix Web hits the top of framework benchmarks.
Rust doesn’t have anything that can compete with the ecosystem of frameworks like Django and Rails, though. Since Rust is a rather young language, a lot of handy utility libraries are missing, which means that the development process is not that simple and easy.
More on web development in Rust: Are we web yet?
TL;DR Rust is a powerful tool for writing memory-safe and thread-safe applications while keeping it fast. While it has great potential, it is unclear whether the choice of Rust is warranted in fields where considerable library support is needed right at this very moment.
Data Ownership Model
Let’s dive into one of the things that make Rust special — its borrow checker.
To start explaining data ownership in Rust, I need to introduce you to two kinds of memory in low-level programming: the stack and the heap.
Stack is used for static memory allocation, while heap is used for dynamic memory allocation. In simpler words: stack is for things whose memory size we know (like integers or str, which in Rust is a string-in-memory), while heap is for things whose size might change significantly (a regular String). To operate with these mutable things, we allocate space for them on the heap and put a pointer to that space on the stack.
Moving
But there’s a problem: What do you do if two variables are assigned a pointer to the same data on the heap?
If we try to change one of the variables by changing the data underneath, the other one will also change, which is frequently not something we want.
The same (and even worse) situation happens if there two threads are operating with the same data.
Imagine if one of these threads mutate the data on the heap while the other is reading from it. Oh, the eldritch horror that can come out of it! We call this a data race.
Therefore, in Rust, only one variable can own a certain piece of data. Once you assign that data to another variable, it is either moved or copied.
To give an example:
let mut s1 = String::from("All men who repeat a line from Shakespeare are William Shakespeare.");
let mut s2 = s1;
s1.push_str("― Jorge Luis Borges");
This won’t compile because the ownership of data gets moved to s2, and s1
can't be accessed after the move anymore.
Borrowing
Now moving the ownership around manually is quite troublesome, since you always need to make sure to give it back.
To solve that, we can borrow variables by creating references to them. Using these references doesn’t transfer ownership, but lets us either read the variable (immutable reference or &
) or even mutate it (mutable reference or mut &
).
But there are limits on references, since having multiple mutable references would amount to the same thing as having multiple owners.
That’s why the compiler enforces a rule for referencing things.
You can do either:
multiple immutable references (read-only),
one mutable reference (read-and-write).
Here’s an intuitive metaphor that I shamelessly borrowed from Rust explained using easy English.
Think of data referenced as a Powerpoint presentation. You can either edit the presentation (mutable ref), or present it to any amount of people (immutable ref), but if it’s presented while it’s being edited, heads might roll in the respective department.
Rust vs. C++
Now that we know what makes Rust special, we can compare it to the other main systems programming language — C++.
Why choose Rust over C++?
In C++, developers have more issues when trying to avoid undefined behavior. In Rust, the borrow checker enables you to avoid unsafe behavior by design. This eradicates a whole class of bugs, and that’s quite important.
In addition, Rust is a much more modern and, in some aspects, better-designed language. In particular, the powerful type system will help you even when its main objective is not to catch memory errors, and being new, it can create its tooling with the best practices in mind without worrying about legacy codebases.
If you don’t want to drop your old C code, Rust has a solution. You can easily call your functions through FFI (Foreign Function Interface). Of course, the compiler can’t guarantee the safety of this code, but it’s a good last resort.
Why choose C++ over Rust?
C and C++ have been around for decades. Whatever problem you want to solve, there’s most likely a ton of libraries by people that have had the same exact problem.
Sometimes, this means that it is impossible to use Rust because it is practically impossible to replicate the ecosystem support. In particular, C++ has game engines and frameworks that we won’t see on Rust for quite some time.
The same problems that Rust solves, modern C++ has solved in (somewhat roundabout) ways, so trusting experienced C++ developers is a reasonably safe option if you do not want to venture in Rust.
And, of course, to write Rust, you sometimes need to wrestle with the compiler. This is not for everyone.
In the end, Rust’s slogan is “a language empowering everyone to build reliable and efficient software.”
While Rust initially started as a replacement for C++, it is clear that they are aiming further, trying to make lower-level programming accessible to more and more people that wouldn’t perhaps be able to handle C++.
This makes the comparison a bit moot. Rust is not a substitute, but a language that opens up new spaces of possibility, one of which we will discuss in the next section.
Rust and WebAssembly
If you haven’t yet heard about it, WebAssembly is like… Assembly for the Web.
Historically, browsers have been able to run HTML, CSS, and JavaScript, with HTML responsible for the structure, CSS for the look, and JavaScript for the interactions. If you didn’t like writing in plain JavaScript, you could transpile it from various other languages that added types, Haskell- or OCaml-like code, and other things.
But, JavaScript does not have the predictably fast performance necessary to run computation-intensive applications like games. (This is due to the garbage collector and dynamic typing.)
WebAssembly helps with that. It is a language for the browser that can serve as a compile target for any language, such as Rust, Python, and C++. This means that you can take code in basically any modern programming language, and put it in the browser.
In comparison to other languages, Rust is ideally suited for writing code to compile to WebAssembly.
Minimal runtime. WebAssembly doesn’t have its own runtime, so it needs to be shipped with the code. The smaller the runtime, the less stuff the user needs to download.
Statically typed. Since Rust is statically typed, it can compile to a more efficient WebAssembly since the compiler can use the types to optimize the code.
We have a head start. Most importantly, Rust has embraced WebAssembly wholeheartedly. Rust already has a fantastic community and tooling for compiling to WebAssembly, which, to be honest, is the most significant advantage out of these three.
For more info on Rust and WebAssembly, watch this talk by Steve Klabnik or check out the rustwasm book.
Getting Started with Rust
To get started with Rust code, you can either download rustup
here or use the Rust Playground, which is an online tool that lets you run some Rust code and witness the consequences.😅
Once you have your Rust environment ready, let’s do some code. Here, we will be doing a Rust version of fizzbuzz to give a brief insight into what Rust is capable of.
To create a new project, go to the directory you want the project to be in and do cargo new fizzbuzz
. This will instruct Rust's build manager to create a new project. Once you do that, go to the/src
folder and open up main.rs
.
First, let’s write something that takes a number and returns:
“fizz” for the numbers that divide with 3,
“buzz” for numbers that divide with 5,
“fizzbuzz” for numbers that divide with both 3 and 5,
the number as a string if it is divided with neither.
Rust has a very powerful tool in match statements to do this:
fn fizzbuzz (number: u32) -> String {
match (number % 3, number % 5) {
(0, 0) => "fizzbuzz".to_string(),
(0, _) => "fizz".to_string(),
(_, 0) => "buzz".to_string(),
(_, _) => number.to_string()
}
}
Since text in quotes is a string in memory, or str
in Rust, we need to convert it to a String.
Now, we need a way to count up to a certain number from 1. We’ll write a new function that takes the number as an argument, creates a range from 1 to the number, applies the fizzbuzz
function, and prints the result. In Rust, we can achieve this with a simple for loop.
fn count_up_to (number: u32) -> () {
for i in 1..=number {
println!("{}", fizzbuzz(i))
}
}
To achieve any result in the terminal, we need to have a main function. Let’s replace hello_world
with this:
fn main () {
count_up_to(100);
}
Now, we can use the cargo run main.rs
command, and most likely, should see a stream of fizzes and buzzes on our terminal.
But hey! Perhaps fizzbuzz isn’t the only game we play? Perhaps the new hotness is wubbalubba? Let’s quickly modify our counting code to make sure we can take on any of the counting games around town.
To do that, we will need our Rust function to take another function that takes an unsigned 32-bit integer and returns a String. After adding what is called a function pointer to the type signature, the worst has passed. Inside, we just need to substitute fizzbuzz with the function variable.
fn count_up_to_with (number: u32, function: fn(u32) -> String) -> () {
for i in 1..=number {
println!("{}", function(i))
}
}
If we add a new game that somehow turns integers into strings, our function will be able to handle it.
For convenience, here is wubbalubba (hardly a creative invention) and the required function to call it:
fn wubbalubba (number: u32) -> String {
match (number * 2 % 3, number % 4) {
(0, 0) => "dub dub".to_string(),
(0, _) => "wubba".to_string(),
(_, 0) => "lubba".to_string(),
(_, _) => number.to_string()
}
}
fn main() {
count_up_to_with(100, wubbalubba);
}
Further Learning
Rust’s community is awesome. Everywhere you go, you will find a lot of clearly-explained, beginner-oriented materials for learning, and people ready to help you:
**Tour of Rust.** An interactive tutorial on the basics of Rust, up until generics and smart pointers.
**The Rust Programming Language.** The Rust book of choice, available online for free.
**Exercism Rust track.** If you want to get more experience with the language and its syntax, Exercism is a good option. Unfortunately, it looks like the Rust track is slightly overpopulated right now, so don’t count on receiving a lot of mentor attention.
**Rust by example.** A collection of examples of idiomatic Rust code.
**Rust cheat sheet.** If you want to take a quick look at the most important concepts of Rust, this is for you.
**Rustlings.** A collection of Rust exercises that will let you wrestle with the compiler in a controlled environment.
I hope that this article helped you understand why Rust is so popular and loved right now. I also hope that I have set you on a path on learning and trying out Rust for yourself, either for a tool or a side project. If you have a question, don’t hesitate to ask: We’re there for you.