Data Types
Rust’s type system is one of the major differences from JavaScript. Let’s explore the various data types in Rust and how they compare to JavaScript.
Type Systems: Static vs Dynamic
JavaScript is dynamically typed, meaning variables can change types at runtime:
let value = 42; // Numbervalue = "hello"; // Now it's a Stringvalue = true; // Now it's a Boolean
Rust is statically typed, meaning every variable’s type must be known at compile time:
let value = 42; // i32 by default// value = "hello"; // ❌ Error: mismatched types// value = true; // ❌ Error: mismatched types
In Rust, you can explicitly annotate types:
let age: u32 = 30;let name: String = String::from("Alice");let is_active: bool = true;
Scalar Types
Integers
JavaScript has a single numeric type (Number
) for integers and floating-point values, plus BigInt
for large integers.
Rust has multiple integer types based on size and signedness:
Type | Size (bits) | Range | JS Equivalent |
---|---|---|---|
i8 | 8 | -128 to 127 | Number |
u8 | 8 | 0 to 255 | Number |
i16 | 16 | -32,768 to 32,767 | Number |
u16 | 16 | 0 to 65,535 | Number |
i32 | 32 | -2,147,483,648 to 2,147,483,647 | Number |
u32 | 32 | 0 to 4,294,967,295 | Number |
i64 | 64 | -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 | BigInt |
u64 | 64 | 0 to 18,446,744,073,709,551,615 | BigInt |
i128 | 128 | Very large range | BigInt |
u128 | 128 | Very large range | BigInt |
isize | arch | Depends on architecture | Number |
usize | arch | Depends on architecture | Number |
let a: i32 = 42; // 32-bit signed integerlet b: u64 = 1000000; // 64-bit unsigned integerlet c = 98_222; // Underscores for readability, like 98,222let d = 0xff; // Hex valuelet e = 0o77; // Octal valuelet f = 0b1111_0000; // Binary value
In JavaScript, all these would be:
const a = 42; // Numberconst b = 1000000; // Numberconst c = 98222; // Numberconst d = 0xff; // Hex value (255)const e = 0o77; // Octal value (63)const f = 0b11110000; // Binary value (240)const g = 9007199254740992n; // BigInt for numbers > 2^53
Floating-Point Numbers
Rust has two floating-point types:
Type | Size (bits) | Precision | JS Equivalent |
---|---|---|---|
f32 | 32 | Single precision | Number |
f64 | 64 | Double precision | Number |
let x: f64 = 2.0; // Double-precision float (default)let y: f32 = 3.0; // Single-precision float
In JavaScript:
const x = 2.0; // Number (always double precision)
Booleans
Both languages have boolean types:
let t: bool = true;let f: bool = false;
const t = true;const f = false;
Characters
JavaScript doesn’t have a dedicated character type; single characters are just strings of length 1.
Rust’s char
type represents a Unicode scalar value:
let c: char = 'z';let z: char = 'ℤ';let heart: char = '❤';
In JavaScript:
const c = 'z'; // String of length 1const z = 'ℤ'; // String of length 1 (but 2 bytes in UTF-16)const heart = '❤'; // String of length 1 (but 2 bytes in UTF-16)
Compound Types
Tuples
Rust has tuples, which are fixed-length collections of values of different types:
let tup: (i32, f64, u8) = (500, 6.4, 1);
// Destructuringlet (x, y, z) = tup;println!("y is: {}", y); // 6.4
// Accessing by indexlet five_hundred = tup.0; // 500
JavaScript added tuple-like behavior with array destructuring:
// JavaScript doesn't have real tuples, but we can use arraysconst tup = [500, 6.4, 1];
// Destructuringconst [x, y, z] = tup;console.log(y); // 6.4
// Accessing by indexconst fiveHundred = tup[0]; // 500
Arrays
Rust arrays have a fixed length and contain elements of the same type:
// Array with 5 elements of type i32let a: [i32; 5] = [1, 2, 3, 4, 5];
// Initialize an array with the same value repeatedlet b = [3; 5]; // Equivalent to [3, 3, 3, 3, 3]
// Access elementslet first = a[0]; // 1
JavaScript arrays are dynamic and can contain elements of different types:
// JavaScript arrays are dynamicconst a = [1, 2, 3, 4, 5];a.push(6); // Can add elements
// Can contain mixed typesconst mixed = [1, "two", true, { four: 4 }];
// Access elements the same wayconst first = a[0]; // 1
For dynamic arrays in Rust, you’d use a Vec
(vector), which we’ll cover in the Collections section.
Strings
Strings are a complex topic in Rust, especially coming from JavaScript.
JavaScript Strings
In JavaScript, strings are simple:
const greeting = "Hello, world!";const name = 'Alice';const template = `Hello, ${name}!`;
// String methodsconst upperName = name.toUpperCase();const sub = greeting.substring(0, 5); // "Hello"
Rust Strings
Rust has two string types: String
and &str
:
// String literals are &str (string slices)let greeting: &str = "Hello, world!";
// String type is growable, heap-allocatedlet mut name = String::from("Alice");name.push_str(" Smith"); // Now "Alice Smith"
// Creating a String from a &strlet s = "initial".to_string();let s = String::from("initial");
// Concatenationlet hello = String::from("Hello, ");let world = String::from("world!");let hello_world = hello + &world; // Note: hello is moved here
// Format macro (like template literals)let name = "Alice";let greeting = format!("Hello, {}!", name);
The main differences:
&str
is immutable and often used for string literals or views into stringsString
is growable, heap-allocated, and owned- Rust strings are UTF-8 encoded, which affects how they’re indexed and manipulated
Type Conversion
JavaScript Type Coercion
JavaScript often performs implicit type conversion:
console.log("5" + 1); // "51" (string)console.log("5" - 1); // 4 (number)console.log(5 + true); // 6 (number)
Rust Explicit Conversion
Rust requires explicit conversion:
let x = 5;// let y = x + "10"; // ❌ Error: cannot add `&str` to `{integer}`
// Explicit conversionlet y = x + 10; // 15let s = x.to_string() + "10"; // "510"let z = x + "10".parse::<i32>().unwrap(); // 15
Special Types
The Unit Type
Rust has a unit type ()
, which represents the absence of a value:
// Functions with no return value implicitly return the unit typefn do_something() { println!("Hello"); // Implicitly returns ()}
JavaScript doesn’t have an equivalent; functions that don’t return anything implicitly return undefined
.
The Option Type
Instead of null
or undefined
, Rust uses Option<T>
to represent a value that might be absent:
let some_number = Some(5);let some_string = Some("a string");let absent_number: Option<i32> = None;
// Using Option requires handling both casesmatch some_number { Some(n) => println!("Got a number: {}", n), None => println!("No number"),}
JavaScript equivalent:
const someNumber = 5;const someString = "a string";const absentNumber = null;
// Check for null/undefinedif (someNumber !== null && someNumber !== undefined) { console.log(`Got a number: ${someNumber}`);} else { console.log("No number");}
Type Inference
Both languages can infer types, but Rust’s inference is more sophisticated and happens at compile time:
// Rust infers these typeslet x = 5; // i32let y = 3.0; // f64let active = true; // bool
// JavaScript also has type inference, but at runtimelet x = 5; // numberlet y = 3.0; // numberlet active = true; // boolean
Type Aliases
Rust allows you to create type aliases:
type Kilometers = i32;
let distance: Kilometers = 5;
In JavaScript with TypeScript:
type Kilometers = number;
let distance: Kilometers = 5;
Next Steps
Now that you understand Rust’s data types, you’re ready to move on to Functions, where we’ll explore how to define and use functions in Rust compared to JavaScript.