Rust for JavaScript Developers - Variables and Data Types
This is the second part in a series about introducing the Rust language to JavaScript developers. Here are all the chapters:
- Tooling Ecosystem Overview
- Variables and Data Types
- Functions and Control Flow
- Pattern Matching and Enums
Variables
JavaScript has three ways to declare variables - var
, const
and let
. Rust also has const
and let
but they work very differently compared to JavaScript.
let
In Rust, we declare variables using let
.
let a = 123;
As expected, this assigns the value 123
to the variable a
. But Rust by default makes the variable immutable. You won’t be able to change the value after it’s assigned.
let a = 123;
a = 456; // Error! :(
At first glance, this might look similar to JavaScript’s const but JavaScript’s const doesn’t make variables immutable, it just makes the binding immutable.
If you want to make variable mutable, you need to explicitly mention it using the mut
keyword
let mut a = 123;
a = 456; // No Error :)
const
Rust’s const
is also very different from JavaScript’s const - it’s better to think of Rust’s const as a “label” to a constant value. During compile time they get replaced by their actual values in all the places they are used. It’s usually used for constants like port numbers, timeout values, error codes etc.
You can also define const outside functions at global level which can’t be done using let.
Data Types
Numbers
In JavaScript, we’ve the Number type for both integers (numbers without decimal point) and floats (numbers with decimal point). In Rust, there’s a ton of options for integers and floats but by default we can use i32
for integers and f64
for floats.
let x = 123; // i32
let y = 4.5; // f64
Booleans
Pretty straightforward - both JavaScript and Rust have booleans with true/false values
let x = false; // bool
Strings
We usually don’t think much about strings when working with JavaScript - they “just work”. In Rust, there are many types of strings but let’s focus on the widely used ones - String
and &str
.
String
is growable whereas &str
is immutable and fixed size.
When you create a string using a string literal, it creates a &str
type:
let name = "Saitama"; // &str
You need to use String::from
or to_string
methods to create a String
type:
let name = String::from("Genos"); // String
let name2 = "King".to_string(); // String
You can convert from String
to &str
using the as_str
function
let name2 = "King".to_string(); // String
let name3 = name2.as_str(); // &str
We’ll learn more about strings in future posts.
Optionals
JavaScript has two types for empty values - undefined
and null
. Undefined is used when a variable, property etc is not defined and null is used when something is intentionally empty.
Rust has neither of these - it doesn’t even have a dedicated null data type. Instead it has something call Option
. When we have a situation where a value can be empty or initially undefined, this Option
type is used.
People who have worked with TypeScript/Flow might see some similarities here but it’s quite different in terms of how the optionals are created and used.
Say we want to write a function that takes in a file path and returns its contents. Let’s say for whatever reason, we want to return a “null” value when empty string is passed as file path.
Here’s how we would write this in JavaScript/TypeScript:
function read_file(path: string): string | null {
const contents = "hello";
if (path !== "") {
return contents;
}
return null;
}
Implementing the same using Rust’s Option
:
fn read_file(path: &str) -> Option<&str> {
let contents = "hello";
if path != "" {
return Some(contents);
}
return None;
}
You can see that we return None
for null value but for non-null value, we don’t return the contents
as it is, but rather we “wrap” it inside Some
and return that. The return type is also not “string or null” as per the TypeScript example but “Option that contains &str” type.
Here’s how you would call this function in JavaScript/TypeScript:
function main() {
const file_contents = read_file("/path/to/file");
if (file_contents !== null) {
console.log(file_contents); // file_contents is refined to string type
} else {
console.log("Empty!");
}
}
Calling the function in Rust:
fn main() {
let file = read_file("path/to/file");
if file.is_some() {
let contents = file.unwrap();
println!("{}", contents);
} else {
println!("Empty!");
}
}
As you can see, we need to manually “unwrap” the Option to get the contents inside.
Arrays
Similar to strings, there are two types of arrays - one with fixed size (simply referred to as “Array”) and other that can grow/shrink in size (called “Vectors”).
Arrays:
fn main() {
let list = [1, 2, 3];
println!("{:?}", list);
}
Vectors:
fn main() {
let mut list = vec![1, 2, 3];
list.push(4);
println!("{:?}", list);
}
Objects
Technically, all non-primitive types are “objects” in JavaScript but we commonly use the term “object” for two things - bag of data or hash map.
Bag of data:
Unlike other languages, you need not go through a lot of ceremony to create an object and this is one of the coolest things about JavaScript.
To create an employee object in JavaScript:
function main() {
const employee = {
name: "Saitama",
age: 25,
occupation: "Hero",
};
}
To create the same object in Rust, we can use structs:
struct Employee {
name: String,
age: i32,
occupation: String,
}
fn main() {
let employee = Employee {
name: "Saitama".to_string(),
age: 25,
occupation: "Hero".to_string(),
};
}
HashMap:
In JavaScript, to create an object with arbitrary key value pairs, we can either use normal object literals or the Map object:
function main() {
const colors = new Map();
colors.set("white", "#fff");
colors.set("black", "#000");
console.log(colors.get("white")); // #fff
}
In Rust, you can do the same using the HashMap type:
use std::collections::HashMap;
fn main() {
let mut colors = HashMap::new();
colors.insert("white".to_string(), "#fff");
colors.insert("black".to_string(), "#000");
println!("{:?}", colors.get("white").unwrap()); // #fff
}
Notice the usage of unwrap above. HashMap’s get method returns an Option type which we need to unwrap to get the value inside.
Thanks for reading! Feel free to follow me in Twitter for more posts like this :)