Rust for Node.js developers

Are you a Node.js or JavaScript developer who’s interested in the Rust programming language, but don’t know where to start? Curious how you can translate your Node.js and JavaScript knowledge into “Rust speak”. In this article, I walk you through some of the most noticeable differences between JavaScript and Rust and how they relate to one another, so that you have a better idea of what to expect when you start using the Rust language.

Type system

Let’s get the most massive difference out of the way first: the type system. For those of you coming from JavaScript and not TypeScript, I know hearing about a type system can create a lot of anxiety. Don’t worry, Rust’s system is one of the easiest and most expressive I have ever worked with!

Rust is a static and strongly typed language, while JavaScript is a dynamic and weakly typed language, so let’s take a look at what that means and how that affects your everyday code!

Static vs. dynamic typing

Static typing simply means that all types of the variables must be known at compile time and not at runtime. This means that values in statically typed languages aren’t dependent on user input, able to change at runtime, or anything else not known at compile time.

Rust uses static typing while JavaScript uses dynamic typing. Let’s look at how that translates to a bit of code:

JavaScript (Dynamic)

let a = 3 // a is a `number` type
a = "Hello, world!" // This is perfectly valid, and a is now a `string`

Rust (Static)

let mut a = 3; // a is now an `i32` value
a = "Hello, world!"; // Compiler Error: The type cannot change at runtime.

One interesting point to note about the above Rust code is that, in contrast to other statically typed languages like C++ or Java, Rust actually has more advanced type inferencing capabilities. This means that, often, you don’t have to specify the type of the variable except in your structs and function definitions. I think this is a nice and balanced approach: It’s easy to understand for anyone looking at a function signature, but the person implementing the function doesn’t need to worry about typing everything within it.

Another point I’d like to bring up is that Rust actually does have a pattern for dealing with a variable whose type you may wish to change over the course of a function’s execution. For example, if your function has a number, but then you want that number as a string for another operation, here’s how you can accomplish that.

let num = 10 * get_random_number(); // num is an `i32`
let num = num.to_string(); // num has been shadowed and is now a `String`

This pattern is referred to as shadowing in Rust. You’ll see this very common pattern in many Rust codebases. The reason this works is that internally Rust essentially creates a new variable every time you use the let keyword, so it doesn’t care if the assignment has a different type to the variable before it. As far as Rust is concerned, it’s just another new variable like any other.

Now that we’ve taken a look at how Rust’s static typing differs from the dynamically typed JavaScript, let’s take a look at the resulting differences between strong and weakly typed languages.

Strong vs. weak typing

While the comparison between static and dynamic typing is clear (types are either known at compile time or they are not), strong and weak typing is a little more nebulous as those types are more like a scale that languages can fall along. That’s because those terms are measuring how much a language allows implicit conversions between one type and another.

If a language is more strongly typed, then it allows fewer cases of implicit conversions than a language with many instances of these conversions. Given that definition, Rust ranks VERY high on the strong scale as there is only one instance of implicit conversion, and this conversion can only be accessed under very specific circumstances.

On the other hand, JavaScript, ranks high on the weak scale as there are implicit conversions everywhere. In fact, whether you know it or not, every variable you assign has a certain element of implicit conversion. That is because every variable you declare is actually an object with additional properties given by the JavaScript runtime.

So, how does that affect your code?

Let’s take a look at two equivalent pieces of code in JavaScript and Rust.

JavaScript (Weak)

// Implicitly converts num to a `string`
const num = 10 + "0"

// Prints "Num is a string with a value of 100"
console.log(`Num is a ${typeof num} with a value of ${num}`)

Rust (Strong)

#![feature(type_name_of_val)]
use std::any::type_name_of_val;

// No implicit conversion. You have to manually make 10 a `String` first
let num = 10.to_string() + "0";

// Prints "Num is a std::string::String with a value of 100"
println!("Num is a {} with a value of {}", type_name_of_val(num), num);

As you can see in the above code, JavaScript had no problem with implicitly converting a number into a string. With Rust, you have to explicitly note that you are transforming 10 into a string before adding on a string slice containing “0”.

While each person may have their preferences, I personally prefer to have that explicitness in my code, especially when working on larger code bases where you haven’t written all of the code yourself. That clarity makes it easier to reason about the intentions of the original author and prevent mistakes, like wondering why your number is now 100 instead of 10 after adding 0.

Now that we’ve taken a look at some of the differences in the type systems, we’ll take a look at another very important topic, mutability.

Mutability

As you know, in modern JavaScript, there are three different ways to declare a variable:

  • let
  • const
  • var

While each one of these work differently, for the purpose of discussing mutability, I’ll refer only to let and const since both let and var function identically in terms of mutability.

In Rust, there are three ways to declare a variable:

  • let
  • let mut
  • const

They all function differently as far as mutability is concerned. Looking at the different options of declaring a variable in Rust, it’s fairly easy to understand that let mut declares a mutable variable, but if that’s the case, what’s the difference between let and const? Also, why do you have to use two words to express a variable should be mutable?

To answer the first question, you need to remember that Rust, unlike JavaScript, is a compiled language. This fact is where the main difference between let and const comes into play. Even though they are both immutable, the difference comes down to when the variable is initialized (Note: I specifically use initialized here instead of declared).

A const variable must be initialized at compile time, and every occurrence of that variable is actually inlined at the site it is referenced. A good example of this is the value PI. You would likely want to maintain that value throughout your application and keep it the same. On the other hand, a let variable allows you to initialize it to a variable only known at runtime, such as user input. While that answers the first question, you may still be wondering why it takes more effort to create a mutable variable instead of an immutable?

Well, Rust draws heavily from the functional programming paradigm, and in pure functional programming, every value is immutable. While this greatly increases CPU load and memory consumption, there are quite a few benefits, and while I can’t go over them all here, one of the biggest advantages is when trying to reason about concurrent/parallel code. Rust does not follow pure functional programming, so you can still declare a mutable variable for performance reasons, but they want to make you think about whether you really do need that variable to be mutable.

Now, let’s look at the last major difference between Rust and JavaScript: nullability.

Nullability

The inventor of null, Sir Tony Hoare, is famously quoted as saying it was his “billion-dollar mistake”. He calls it a mistake because he estimates that over the past 40 years, null references have led to “innumerable errors, vulnerabilities, and system crashes…” costing upwards of a billion dollars.

I’m sure that any developer who uses a language that permits null references has had a null exception at some point in their career. I most certainly have! In JavaScript, though, that distinction becomes even more confusing when you realize that you could not only be passed a variable that is null, but also one that is undefined. Having to check against both of these instances can be incredibly confusing, and is only enforced at runtime, leading to many instances of failed execution. Thankfully though, Rust does not handle the concept of nothingness like this.

Rust’s answer to nullability

In Rust, there are two main ways to handle a potentially nothing value:

  • Option<T> type is used when it’s not necessarily an error for nothing to be there
  • Result<T, E> type is used when it is an error for that value to not exist

In a future tutorial, I’ll show you in more detail how to use these patterns, but for a quick example to showcase the difference, let’s examine how that might work when working with an API. If a particular field of an object returned from a database can be null, then you might use an option type to represent that situation, but if you’re calling an API and expect there to be a response, then you would use a Result type to model that behavior.

Summary

Although there are far more comparisons that could be made, I hope you now better understand how the fundamental language features of Rust relate to JavaScript! I also hope that you are perhaps less timid about giving Rust a try. While it does take some getting used, the reward is well worth it, and you may find that your JavaScript code becomes even better as a result.

Also, stay tuned for the next post which will be a tutorial that goes over some of the more low-level comparisons between the languages by building an API in Rust! This tutorial will cover concepts like tuples, arrays, hashmaps, and string manipulation!