Intro to Rust: The Modern-Day, Safe Low-Level Programming Language
Learn why Rust is a modern, memory-safe systems language that is a good alternative to the older languages that traditionally filled this space.
Rust is a relatively new open-source language that has been making waves in recent years. It’s been the number one loved language according to the Stack Overflow Developer Survey for five years running now. Anecdotally, it’s started popping up on my own radar pretty often in the last couple of years.
So why does everyone love Rust? I decided to find out for myself and this is what I learned.
What is Rust?
Rust is a compiled language with a focus on speed, memory safety and parallelism. It originally started as a project at Mozilla, and while they still sponsor the project it has become fully open source. Rust is aimed at being a modern-day systems programming language. It is low level enough to provide a lot of control, it compiles down to the smallest binary possible and runs very fast with little overhead.
For decades the most widely used system language that filled these requirements was C or C++. These languages are loved by many and have stood the test of time, but many believe that they are showing their age.
Anyone who has worked with C++ knows that it does not hold your hand. C++ has no garbage collector and memory management must be done manually. This introduces many vulnerabilities that a programmer can walk into without knowing it. Things like use after free, double free, memory leaks, null pointers and data races are all examples of the kind of hard-to-track-down bugs that manual memory management can lead to. Keeping track of all of these gotchas just to write a program that is memory safe, as well as juggling all of the requirements in your problem domain can be a tall order for even the most experienced programmer.
As a matter of fact, there was a study done by Microsoft in 2019 and they found that a whopping 70 percent of all security vulnerabilities in their production code were made possible due to memory safety bugs. Very interestingly, Google reported the same number for security vulnerabilities with Chrome.
Carol Nichols, a member of the Rust Core team and co-author of The Rust Programming Language book, gave an excellent talk where she compared the development of Rust to old trains. Old trains used to have to be stopped manually. Brakemen as they were called, had to physically go up to the top of trains, walk to the end of each car and manually apply the brakes whenever the train needed to stop. As you can imagine, this was a very dangerous job and not an exact science. It wasn’t until a man named George Westinghouse invented a new tool, the airbrake, that this process was automated and made much safer.
Rust is that new tool for software development. Rust has airbrakes.
What's different about Rust?
The main way Rust accomplishes this is with a new way of thinking about memory management. Instead of using a garbage collector at runtime, Rust has a system of “ownership” and “borrowing” of variables which is checked at compile time. According to The Rust Programming Language book, the concept of ownership has 3 rules:
- Each value in Rust has a variable that’s called its owner.
- There can only be one owner at a time.
- When the owner goes out of scope, the value will be dropped.
The variable x is valid when it is defined in the scope of greet and becomes invalid after the block ends. Rust will then make sure that the memory for x is automatically cleaned up. And again, the key difference between what Rust is doing here and what a garbage collected language does is that Rust figures out when the memory for x needs to be dropped at compile time. This means you don’t have the runtime overhead of a garbage collector trying to figure it out dynamically as the code runs, which keeps Rust fast.
The above example demonstrates #1 and #3 of the ownership rules, but not #2 which says that “there can only be one owner at a time.”
Here we assign the string value to y, which “moves” ownership of it from x to y. We then try to use x after we moved ownership. Rust will not let this code compile. It will say that you cannot borrow a value after it has been moved somewhere else. This shows that there can only be one owner of a value at a time. But what if we needed y to read the value of x for some reason? We can do this, but we have to explicitly tell Rust that y is only going to “borrow” the value of x, meaning it only can only read the value, and never change it.
Here we tell the Rust compiler not to worry, y is only borrowing the value of x, and will never change it, so this code compiles just fine.
The key here is that through this system of ownership and borrowing, Rust keeps the advantages of a language like C++ in that there is no garbage collection slowing down run time, while also eliminating the disadvantages of needing to know the ins and outs of manually managing memory.
Ownership and borrowing aren’t the only ways that Rust keeps your code safer. There’s also data immutability by default. You can declare a variable as mutable, but importantly there can only ever be one mutable reference of data. This means you don’t have to worry about the data held in variable x being somehow modified by variable y, a common problem seen in other languages.
It’s also important to note that all of these rules also apply to concurrency. It’s hard enough to keep track of all these gotchas in a single-threaded program, let alone in a multithreaded one, which is why the Rust team says that Rust boasts “fearless concurrency.”
What's the catch?
Just like with any language, there is a tradeoff. With Rust, this tradeoff is that you need to become familiar with a different way of writing code. Learning the rules of ownership and borrowing can take some time, so at first writing code in Rust can feel cumbersome. But the prize for learning these rules is worth it: you can be sure your code is memory safe — theoretically eliminating a large percentage of security vulnerabilities long before the code is shipped to production.
The sooner you catch an error the lower the cost is to fix it. While it might sometimes seem frustrating that the Rust compiler keeps yelling at you saying you got it wrong, it’s really a good thing because those are all problems you won’t have to fix later after your code has shipped. It feels good when your Rust program compiles without a problem because you feel a lot more confident that the code is “correct,” and holds far fewer hidden problems.
Having a language that focuses on safety and correctness as well as speed and syntax is a great thing. As time goes on the world depends on software more and more to handle everything from our entertainment to our finances. We can no longer afford to be shipping code at this scale that could potentially be riddled with mistakes and memory vulnerabilities for hackers to exploit. We need languages like Rust that catch the big mistakes right when the program tries to compile.
This makes financial sense as well as technological sense. How much time and money would have been saved if Microsoft had caught those memory safety issues early on, not only for Microsoft itself but for the people and businesses using Windows? And how about Google, who has also struggled with security issues due to memory management problems in Chrome?
I encourage you to check out Rust as an alternative to C or C++ in the future. While the language is admittedly young and doesn’t have the extensive lifespan and collection of frameworks and libraries of the older languages, it’s developing fast and has a great community behind it. Check out the excellent Rust Programming Language book and get to know the community. As software creators, we owe it to ourselves and to our users to adopt new tools that learn from the mistakes of the past. Even if Rust doesn’t end up being that tool, it undoubtedly has brought some really good ideas to the table.