Recently, I have been tinkering with an RPi Zero 2 W board: making it serve a small app on my local network, running a lightweight LLM model to see how long it takes it to respond to a prompt (16 minutes 😢), and just the kind of experiments that you would run on a microcomputer of that scale.
Being a full-stack developer, I wondered if I could use such a low-spec microcomputer as a server for a small internal tool. My first thought was to create that app in Ruby on Rails, and my second thought was to use SvelteKit. But I wasn’t sure if either stack would be ideal for a microcomputer with half a gigabyte of RAM.
So, instead, I decided not to go with either and just sated my curiosity by learning Rust.
I have been tinkering with low-level programming languages like C for almost half a decade now; even today, if you go to my personal GitHub account, the oldest repo you’ll find is a small learning app I had built with C. So, my curiosity about what is widely (and controversially) considered a successor to C was natural.
I have been learning Rust for less than three months now, and some of the in-built APIs have been blowing my mind since I came across them. So, I wanted to document these Rust features into a single article and view them from the perspective of a JavaScript/Ruby developer.
Fair warning, while I will be talking about Rust throughout the article here, this is not me advocating to my fellow JavaScript/Ruby developers to give Rust a try. There are quite a few prerequisites that you need to check before dipping your toes into a low-level language like Rust, lest you won’t have fun learning it.
Another fair warning, I am, by no means, an expert in Rust, as I have been a Rustacean for just a little over three months at this point. So, if I have misinterpreted something, please be kind to me.
I’m done with my preface! Let’s move on to 5 Rust features I wish JavaScript and Ruby had!
I know! A command as part of a section heading? What a weird way to start, right? But that was the first thing that blew my mind when I was learning about Cargo, Rust's default build system/package manager.
Cargo is your go-to tool when you are working on a Rust project. It compiles and builds your code for you, downloads and manages any dependencies you would add to your project, and uploads your library crates to Rust’s official package registry, crates.io (the JavaScript counterpart would be npm).
However, we won’t focus on the above Cargo features in this section; stellar as they are! We will be talking about this neat little command: cargo doc --open
When you run this command, Cargo bundles all the documentation from your local package and all its dependencies. This documentation is stored locally in your project’s target/doc directory. The --open flag opens this documentation in your default browser once it is successfully built.
You might wonder what is so special about documentation. For that, let me tell you a dilemma I faced a couple of days ago while working on a React project that started in 2022. The project depended on the @reach/router package for handling in-app navigation. Unfortunately, Reach Router and React Router merged, and React Router remained the surviving package.
Some would consider this a natural progression of how two similar tech projects evolve. I would be one of those people if I wasn’t working on an older version of Reach Router and trying to find a way to open an internal <Link> in a new tab. Because React Router was the surviving project, I had to dig into forum posts and deprecated Reach Router documentation, only to discover it was impossible to do what I was trying to do.
Experience seamless collaboration and exceptional results.
Trust me, the frustration of searching through documentation and Stack Overflow answers only to hit a dead end kills the vibe!
As for the output of cargo doc --open, all the documentation is bundled from the respective crates, so no internet connection is needed. Even if my package relied on a crate that's been archived for a long time, I'd still be able to access its official documentation locally without opening a browser tab. I mean, Cargo would open it for me.
To get the same results but for Rust’s standard library, you’ll need to run the command rustup doc, and you are all set!
Any seasoned JavaScript developer worth their salt would tell you how grateful they would be if this method of documentation got introduced in the JavaScript ecosystem. For those of you who will come at me with pitchforks and arguments like JSDoc/TypeDoc already does the same thing, you, the developer, still need to manually link external library documentation. Standard library documentation is accessed online via MDN. So, not the same!
The same goes for Ruby! While you have RDoc and YARD to generate documentation for your project, the documentation (at least of the gems) has to be generated manually. At least Ruby standard library documentation is available locally via the command: ri --server. However, this command doesn’t automatically open the local documentation on the browser; you must navigate to the relevant port. So, not the same!
What the section heading says! In Rust, all variables that you create are immutable by default. If you try to reassign a different value to that variable, the compiler will throw an error that will sound like this: cannot assign twice to immutable variable
But why is this a feature? Shouldn’t this be counted as a handicap? Well, that depends on what is important to you. If you want to write concurrency-safe and predictable code, you will appreciate immutable variables. If you know anything about bugs caused by data races and accidental data modification, you’ll understand why such a feature would be a godsend.
Plus, Rust requiring developers to explicitly add the mut keyword to any variable that they want to turn mutable makes sure that the developers think long and hard about when and why data should be modified in their programs.
Even from a readability perspective, this is quite useful, as you can tell simply by looking at a variable’s definition if the variable is going to throw any curveballs at you down the line somewhere.
Having said that, and keeping the article’s raison d'être in mind, would it be a good idea to introduce immutability in dynamic languages like JavaScript and Ruby? Well, no. See, even if both JavaScript and Ruby had immutability built into them from their inception, there would still be challenges—challenges such as the learning curve for developers! Imagine you are a newbie JavaScript developer who’s trying to learn new concepts about the language. Now imagine being restricted by immutability from modifying any variable as or when you see fit.
See, mutability by default is so ingrained in most popular programming languages that even the thought of immutability always being a part of such languages from the very beginning makes us wonder how difficult it would have been to pick up these languages as easily as you did.
While I'd love to see immutability by default in JavaScript and Ruby for the many benefits and optimizations it could offer, if I take off my rose-tinted glasses and face reality, I’d prefer the mutability and accessibility of these languages over immutability with the added complexities it brings.
In Rust, you are allowed to declare a new variable with the same name as a previous variable. Sounds weird, right? Let me demonstrate it with a small code snippet:
// Create a new variable with the name given_string
let given_string = String::from("Hello, world!");
// Usually, in JS or Ruby, you would create another variable
// with a different name like given_string_len in this case
//
// But in Rust, due to variable shadowing, you can safely use the same variable
// name and even assign it a value with a different type
let given_string = given_string.len();
println!("{given_string}"); // 13
Here, the second given_string definition shadows the first one. As a result, the second given_string variable is used, and the first given_string variable becomes inaccessible. Usually, you would use this feature in cases where you want to reuse the same variable name and assign it a new value, probably of a different type.
Now, granted that both JavaScript and Ruby allow variable shadowing. But due to the presence of scopes in both of these languages, you end up writing code that looks a little something like this:
let string = "Hello, World!";
let stringLen = string.length;
console.log(stringLen); // 13
Even though it is completely possible to use the same variable name in line 2, without the let keyword, to get the same result, most of us end up creating a new variable with a new name due to the chaotic nature of variable shadowing in both JavaScript and Ruby.
I wish there was a way to make block-based behavior between variables more predictable in JavaScript and Ruby. That would certainly make variable shadowing more reliable.
Null is a value that means there is no value there. In JavaScript and Ruby, variables can either be null or not null. Rust doesn’t have that, however. Instead, Rust uses the Option type to represent that there may or may not be a value present. Through the Option type, Rustaceans avoid the usual pitfalls related to null pointers, such as null pointer dereferencing, leading to runtime errors.
// A function that accepts an option parameter of type Option<i32>
// i32 = Signed 32-bit integer
fn get_value(option: Option<i32>) {
// We use a match expression to compare the value option represents
// with all possible patterns
//
// As you can see, even when there isn’t a value present, we can
// gracefully handle the case
match option {
Some(v) => println!("The value is: {}", v),
None => println!("No value."),
}
}
As you can see from the above code snippet, using Option<T>, Rust forces developers to explicitly handle the possibility of null, which helps them make their code avoid any runtime errors in production.
Experience seamless collaboration and exceptional results.
Unfortunately, neither JavaScript nor Ruby enforce null safety in the same way as Rust. However, workarounds can be employed to get the same functionality as the Option type.
In JavaScript, you can either simulate Option using a library like fp-ts or create your own custom implementation of the Option type. In Ruby, unfortunately, there is only the custom implementation way of mimicking the Option type.
Hopefully, both these languages receive a built-in solution, like the Option type, to handle null safety without any hacky workarounds.
Let’s revisit my use case of wanting to serve a small internal tool from my 512MB RPi board. When I tried installing Rust on that board, it completely crashed, even though I used a non-GUI setup. The reason? Unfortunately, Rust installation requires more than half a gig of space.
Now, if this had happened had I tried to install the Node.js runtime on the board, I would have had to rewrite my entire codebase in a different language because without the JavaScript runtime being installed on my Pi, it would not be able to run my app. The same goes for Ruby!
But the thing about Rust is that, even if Rust is not installed on my board, it can still serve my app as long as I compile it on my workstation and then execute the binary on my board. This makes my Rust app way more portable compared to JavaScript/Ruby or any interpreted language for that matter. Plus, since I didn’t need to install Rust on my board, it led to the efficient performance of my app.
This feature, similar to the immutability feature, falls under the architecturally impossible category. Since neither JavaScript nor Ruby is a compiled language, there is no way to emulate this behavior in either of these languages. So, while I wish something like this could be possible to bring into the JavaScript/Ruby ecosystem, all I can do is bite the bullet and do things how they are supposed to be done here.
Rant: Is it just me, or do other Ruby developers also face issues getting their Ruby environment up and running, even after Dockerizing the project?
I still have a lot to learn and a lot to experiment with before I can say I am confident in Rust. Based on what I've experienced so far, I'm starting to like the language and naturally find myself comparing it to JavaScript and Ruby, as those are the two languages I feel most confident with.
Writing this article gave me a lot of clarity, not only about Rust but also about JavaScript and Ruby, which was my goal from the beginning. Whether you fully agree with everything I've written in this article or not, I hope it has provided you with some clarity about these three languages, no matter your level of confidence in them.