Recently, I decided to take some time to learn the Rust programming language. In my day-to-day job as a machine learning engineer working in bio-tech, largely using Python, I’ve started to notice the limitations and faults of using weakly-typed poor performance languages for production. Python code is easy to iterate on, but very, very slow (upwards of 100x slower than C/C++/Rust in some cases). At the same time, I have pretty negative memories of the first programming language I ever learned - C++. The 2020 C++ standard is currently 1853 pages long.
For this article, I won’t write too much about the syntax or the details of Rust. There are plenty of resources online to get started. Instead, in this series, I’ll write about my rationale for getting into Rust and some of the lessons and pitfalls along the way.
Why Rust?
One big motivator for learning Rust was to better understand why it has become so darn popular. Rust has been the most loved programming language according to Stack Overflow surveys for 7 years straight.
Additionally, around the time I started on my Rust journey, CTO of Microsoft Azure, Mark Russinovich tweeted the following
Additionally, the Linux Foundation began to accept Rust contributions to the kernel for the first time starting in September of this year (2022).
But Rust is hard?
Counter-intuitively, Rust is known for its steep learning curve. Basic operations which can be quite easy in other programming programming languages, like assigning a variable to another variable, or making doubly-linked list, can be quite a bit more involved in Rust than in say Python or C++.
At the same time, there is currently no leading major corporate patron for Rust. Rust was developed in the halls of Mozilla, but the bulk of the team that developed Rust effectively laid off in 2020. It’s original use-case, the (Servo Browser Engine) was effictively abandoned. Unlike C# (Microsoft), Java (Sun Microsystems), Golang (Google), Swift (Apple), TypeScript (Microsoft), or Dart (Google again), Rust currently has no leading corporate cheerleader. Hell, Graydon Hoare, the creator of Rust even went off to work at Apple for several years on Swift!
So what gives? Why does a “hard-to-learn” language that doesn’t have official megacorp backing thrive in the open-source community?
Really, why Rust?
I have learned that Rust (despite its foibles) is loved for the following reasons:
It’s low level, fast, memory safe and not garbage collected
Virtually all the major corporate-backed languages I mentioned previously are garbage collected. In today’s world, if you want to write code in a non-garbage collected language you are left with either C or C++. C is 52 years old and C++ is 37 years old today! A garbage collected language like Java, C#, Swift, Dart, Typescript or Golang will automatically take care of memory management (allocating and freeing up computer memory), but as a result, programs written in these languages will run 3-10x slower than the equivalent program written in C or C++. In contrast C/C++ leaves the programmer capable of manipulating memory directly. Many of the worlds most severe and costly security issues (like Heartbleed, which cost >$500 million to deal with worldwide) originate from C/C++ memory bugs!
Rust is not garbage collected but takes a completely novel approach to memory management through “ownership”. In a nutshell every memory address can only be “owned” by one variable at a a time and gets cleaned up once you hit a right curly brace }
(it’s more complicated than that, but we’ll get to that later). This is a radical approach to memory management, that greatly reduces the risk of memory leaks while not being overtly disruptive the the programming process.
The ‘batteries included’ aspect
The most interesting thing about Rust is how easy it is to get started with the language. cargo
the package manager that comes with the Rust ecosystem is amazingly ergonomic (coming from someone who works with Python and its mishmash of “virtual environments”!). For most projects, you simply edit a toml
file to include dependencies and use cargo run
or cargo build
. Unlike the C# dotnet
ecosystem there isn’t a bunch a baggage (XML files, Mono versus .NET?) to have to sort out before getting started. Unlike C, the standard library has all of the typical stuff you’d want to be productive (such as a standard HashMap implementation). Much like python, there is also a large array of community provided crates
that cover everything from front-end to backend and everything in between. Unlike python, the import system and package management are designed in a sane way! At the same time, the language is low level enough that you can use it to program microcontrollers and embedded systems without losing performance or security along the way.
It’s opinionated in a good way
Based on my (not entirely comprehensive) software engineering experience, the past 50 years of computing have demonstrated a few things that a lot of experienced software engineers can agree on:
- Inheritance bad
- Composition good
- Null values are bad
- Memory safety is important
There are more good things to talk about with Rust here, but for the above four points, Rust takes the high road towards pushing developers to write more robust, maintainable code.
Much like object-oriented (OOP) languages, Rust lets you use “classes” in the form of “structs” which can hold stateful data and have methods attached, but unlike most OOP languages (and following in the footsteps of Golang), Rust does away with class inheritance. This mitigates some of the largest downsides of OOP which has been referred to as a trillion dollar disaster. By removing inheritance from the equation, there is no longer any diamond inheritance problem nor is a bunch of unnecessary state carried over from parent to child classes. Removal of “hidden state” makes writing multi-threaded code much more straightforward in Rust over OOP languages.
Rust encourages programmers to use “traits” instead. “Traits” are similar to the “interfaces” you might see in other programming languages like Java, but much more powerful. A Rust trait can be implemented for any type. For example you can create a trait for a 3 member array like so:
impl Vec3 for [f32; 3]{
fn x(&self) -> f32 { self[0] }
fn y(&self) -> f32 { self[1] }
fn z(&self) -> f32 { self[2] }
}
This encourages code to be decoupled and more compositional.
Additionally, Rust handles null values in a very elegant way. The inventor of the null reference, Tony Hoare, has famously referred to his creation as a billion dollar mistake. Namely because, once you include it in the programming language, virtually function needs to check for it! Failure to account for null references has been the cause of countless programming errors over the decades.
Rust handles this quite elegantly, not by eliminating it wholesale, but making it explicit in the form of a special enum:
enum Option<T> {
Some(T),
None,
}
Thus, the fact that a value can be null is surfaced through its type signature. Null references are surfaced explicitly to the programmer!
Finally (and most importantly), is Rust’s novel way of dealing with memory management through “ownership”. The official Rust book can explain this concept better and more thoroughly than I can, but the basic idea is that each variable can only have one “owner” at a time. Once the right curly brace }
is reached for a given “owner”, that “owner” goes out of scope and the memory gets freed. No manual memory management nor periodic garbage collection sweeps are needed here!
Additionally, for special cases where a variable might want to have more than one owner, there are special smart pointers like Rc
or Arc
that can be used to support special use-cases (much like in modern C++). This approach to memory management is what has allowed Rust
programs to achieve the speed of C/C++ programs while remaining comparatively safer from a memory perspective.
Rust Difficulty - Compile-time oriented code and static analysis
To address the difficulty of Rust - if we were living in 1980, and I was editing Rust code on a black-and-white terminal without having any static analysis aides, I think Rust would be a nightmare compared to C++ due to the high cognitive load of keeping tabs on variable borrowing and lifecyles.
However, the simple switching on of the rust-analyzer
plugin for VSCode changes the situation entirely. Because Rust is so heavily oriented towards computing on the “stack” (which can be handled at compile time) rather than on the “heap” (which must be handled at runtime), Rust static analysis tools basically slap you at every turn when you do something wrong, and you can see the output almost immediately in the editor on a modern computer. One week of this “negative reinforcement” got me to internalize the dynamics of mutable variables, borrowing, and even more advanced topics like generic “traits”.
This tends to make writing certain types of data structures such as doubley-linked lists harder, but at the same time, there are many crates that can be imported to handle such use cases when programming in the real-world (as opposed to a leetcode-style interview)
In my opinion this is one of the key drivers of Rust adoption. By nudging programmers towards code that friendlier to static analysis and providing those tools out of the box, Rust tends to encourage creation of programs that have fewer bugs.
What to make with Rust?
Having resolved to learn Rust, I was faced with the challenge of what to make with it. It has almost become a meme to “rewrite X in Rust.” My day job does not involve systems-level programming (yet), so I decided to delve into Rust for some fun hobby programming that has some good visual feedback. That is covered in …
from Hacker News https://ift.tt/uUHr8NX
No comments:
Post a Comment
Note: Only a member of this blog may post a comment.