Rust vs JavaScript: Syntax and Mental Model Differences
Comparing Rust and JavaScript/TypeScript: variables, mutability, functions, error handling, objects vs structs, and memory management.
For developers transitioning from JavaScript or TypeScript to Rust, the syntax looks superficially familiar (curly braces, let, arrow closures). However, beneath the surface lies a fundamentally different execution model and memory architecture.
Explain Like I’m 10: Rented Apartment vs Owning Real Estate#
- JavaScript (The Managed Apartment): You move in, use electricity and water freely, and leave trash in the hallway. A background landlord (the Garbage Collector) walks around periodically to clean up whatever you forgot. If you access something missing, you get
undefinedornull. - Rust (The Precision Workshop): Every single tool has a designated peg on the wall and an explicit owner. You must account for how things are moved or borrowed. Nothing is cleaned up by a background cleaner—instead, items self-destruct the exact millisecond they leave their workspace room (compile-time deterministic destruction).
graph TD
subgraph JavaScript ["JavaScript Runtime Model"]
JSEval["Dynamic Types & Objects"] --> JSHeap["V8 Heap Memory"]
JSHeap --> GC["Background Garbage Collector (Stops the world periodically)"]
JSEval --> JSNull["null / undefined (Runtime TypeError)"]
end
subgraph Rust ["Rust Compile-Time Model"]
RustEval["Static Strict Typing"] --> RustCheck["Borrow Checker & Type System"]
RustCheck --> Bin["Direct Machine Code (Zero-cost, No GC, RAII deterministic drop)"]
RustEval --> RustEnum["Option<T> & Result<T, E> (Compile-time verified)"]
end
1. Variables and Mutability#
In JS, const prevents reassignment, but inner object properties can still mutate. In Rust, let is completely immutable by default—including deep data.
// JS: let is mutable, const prevents re-binding
let x = 10;
x = 20; // OK
const user = { name: "Alice" };
user.name = "Bob"; // OK! Object contents can mutatejavascript// Rust: immutable by default, requires 'mut' for mutation
let x = 10;
// x = 20; // ❌ Error: cannot assign twice to immutable variable
let mut y = 10;
y = 20; // ✅ OK
struct User { name: String }
let user = User { name: String::from("Alice") };
// user.name = String::from("Bob"); // ❌ Error: cannot mutate immutable bindingrust2. Functions and Implicit Returns#
In Rust, the last expression in a block without a semicolon is returned automatically.
// JavaScript
function add(a, b) {
return a + b;
}
const multiply = (a, b) => a * b; // Arrow implicit returnjavascript// Rust: type annotations required; omitting ';' returns value
fn add(a: i32, b: i32) -> i32 {
a + b // implicit return (no semicolon!)
}
// Closures
let multiply = |a: i32, b: i32| a * b;rust3. Objects & Classes vs Structs & Traits#
JavaScript uses Prototype/Class OOP with this. Rust separates state (struct) from behavior (impl) and interfaces (trait).
// JavaScript Class
class Rectangle {
constructor(width, height) {
this.width = width;
this.height = height;
}
area() {
return this.width * this.height;
}
}
const rect = new Rectangle(10, 20);
console.log(rect.area());javascript// Rust: Struct (data) + Impl block (behavior)
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
// Associated function (constructor convention)
fn new(width: u32, height: u32) -> Self {
Rectangle { width, height }
}
// Method taking reference to self
fn area(&self) -> u32 {
self.width * self.height
}
}
fn main() {
let rect = Rectangle::new(10, 20);
println!("Area: {}", rect.area());
}rust4. null & undefined vs Option<T>#
JavaScript programs frequently crash at runtime with TypeError: Cannot read properties of undefined. Rust completely eliminates null at compile time using Option<T>.
graph LR
JSNullCheck["JS: user?.address?.city"] -->|Undefined at runtime| Crash["Uncaught TypeError if missed"]
RustOption["Rust: Option<String>"] -->|Compiler forces match / if let| SafeHandling["Guaranteed Safe at Compile Time"]
// JS: May return null or undefined
function findUser(id) {
if (id === 1) return { name: "Alice" };
return null;
}
const user = findUser(2);
// console.log(user.name); // ❌ Uncaught TypeError: Cannot read properties of nulljavascript// Rust: Must return Option<User>
struct User { name: String }
fn find_user(id: u32) -> Option<User> {
if id == 1 {
Some(User { name: String::from("Alice") })
} else {
None
}
}
fn main() {
let user = find_user(2);
// Compiler forces you to handle None!
match user {
Some(u) => println!("User: {}", u.name),
None => println!("User not found"),
}
}rust5. Error Handling: try/catch vs Result<T, E>#
JavaScript uses exception throwing that jumps across stack frames unexpectedly. Rust treats errors as regular return values via Result<T, E>.
// JS: try / catch / throw
try {
const data = JSON.parse(rawText);
console.log(data);
} catch (err) {
console.error("Failed to parse JSON:", err.message);
}javascript// Rust: Result<T, E> and the ? operator
fn parse_number(s: &str) -> Result<i32, std::num::ParseIntError> {
let num = s.trim().parse::<i32>()?;
Ok(num * 2)
}
fn main() {
match parse_number("42") {
Ok(val) => println!("Calculated: {val}"),
Err(e) => eprintln!("Parse failed: {e}"),
}
}rustQuick Reference Comparison Table#
| Concept | JavaScript / TypeScript | Rust |
|---|---|---|
| Typing | Dynamic (JS) / Structural (TS) | Static, Nominal, Strongly Typed |
| Memory | Garbage Collector (V8) | RAII Ownership & Borrow Checker |
| Missing Values | null and undefined | Option<T> (Some or None) |
| Error Handling | try / catch / throw | Result<T, E> with ? operator |
| Async Model | Single-threaded Event Loop (Promise) | Multi-threaded async runtime (Tokio / Future) |
| Package Manager | npm / bun / pnpm (package.json) | cargo (Cargo.toml) |
| Immutability | const (shallow only) | let (deeply immutable by default) |
Summary#
- Rust’s syntax will feel familiar to JS/TS developers, but semantics are strictly enforced at compile time.
- Immutability is the default in Rust, requiring explicit
mut. - OOP classes are replaced with
structdata structures andtraitinterfaces. OptionandResultreplacenull,undefined, andthrow, turning runtime crashes into compile errors.