← Study Notes
🦀 Rust · Beginner Guide

Rust for
TypeScript Developers

A practical guide to Rust from zero, written for developers who already know TypeScript. Every concept is shown side-by-side — what you know, and its Rust equivalent.

Zero to Beginner TypeScript Comparisons Ownership Explained Real Code Examples 2026
Section 01

Why Learn Rust?

Rust gives you the performance of C/C++ with the safety of a modern language — and no garbage collector. It's increasingly used for CLI tools, WebAssembly, systems programming, and high-performance backends.

🦀
The key difference from TypeScript: TypeScript runs on a GC-managed V8 runtime. Rust compiles to native machine code and enforces memory safety at compile time through its ownership system — no runtime overhead, no null pointer crashes, no data races.
Feature TypeScript Rust
Runtime Node.js / V8 (JIT + GC) Native binary (no runtime)
Memory management Garbage collected Ownership (compile-time)
Type system Structural, optional strict Nominal, always strict
Null safety null / undefined (strict mode helps) Option<T> — no null
Error handling try/catch exceptions Result<T,E> — explicit
Concurrency Single-threaded event loop Multi-threaded, data-race-free
Package manager npm / pnpm / yarn Cargo
Config file package.json Cargo.toml
Section 02

Setup & Tooling

Installing Rust is simple via rustup — Rust's version manager, analogous to nvm.

Install Rust (macOS / Linux)
# Install rustup (like nvm for Node)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

# Verify
rustc --version    # Rust compiler
cargo --version    # Package manager + build tool
Create a new project (like npm init)
cargo new hello-rust       # creates a new binary project
cargo new mylib --lib      # creates a library project

cd hello-rust
cargo run                  # compile + run
cargo build                # compile only
cargo build --release      # optimized build
cargo check                # type-check without compiling (fast)
cargo test                 # run tests
cargo add serde            # add a dependency (like npm install)

Project Structure

TypeScript
my-project/
├── package.json    # deps + scripts
├── tsconfig.json
└── src/
    └── index.ts
Rust
my-project/
├── Cargo.toml      # deps + metadata
├── Cargo.lock      # lockfile
└── src/
    └── main.rs     # entry point
Cargo.toml (like package.json)
[package]
name = "hello-rust"
version = "0.1.0"
edition = "2021"

[dependencies]
serde = { version = "1", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
💡
Install the rust-analyzer VS Code extension for autocomplete, inline errors, and type hints — it's the Rust equivalent of TypeScript's language server.
Section 03

Variables & Types

Rust variables are immutable by default — the opposite of TypeScript's let. You have to explicitly opt-in to mutation with mut.

TypeScript
// mutable by default
let x = 5;
x = 10;         // ok

// immutable
const y = 5;
// y = 10;    // error

// explicit type
let count: number = 0;
Rust
// immutable by default
let x = 5;
// x = 10;   // ERROR!

// mutable
let mut y = 5;
y = 10;         // ok

// explicit type
let count: i32 = 0;

Primitive Types

TypeScript Rust Notes
number i8, i16, i32, i64, i128 Signed integers
number u8, u16, u32, u64, u128 Unsigned integers
number f32, f64 Floating point
boolean bool true / false
string &str, String Two string types (explained later)
[T, T, T] tuple (T, T, T) Fixed-size, heterogeneous
T[] array [T; N] Fixed-size array
T[] (dynamic) Vec<T> Growable vector (like JS Array)
void () (unit type) Empty return
never ! Diverging (never returns)
Type inference & constants
let x = 42;            // inferred as i32
let y = 3.14;          // inferred as f64
let flag = true;       // bool

// Constants (like TypeScript `const` at module level)
const MAX_POINTS: u32 = 100_000;  // underscores for readability

// Tuples
let point: (i32, i32) = (10, 20);
let (px, py) = point;    // destructuring
println!("{}", point.0); // tuple index access

// Fixed arrays
let arr: [i32; 3] = [1, 2, 3];
let zeros = [0; 5];     // [0, 0, 0, 0, 0]

Shadowing

Rust allows shadowing — redeclaring a variable with let in the same scope. This is different from TypeScript where you can't redeclare with let.

Variable shadowing (unique to Rust)
let x = 5;
let x = x + 1;         // shadows previous x = 6
let x = x * 2;         // shadows again x = 12

// Can even change type when shadowing!
let spaces = "   ";    // &str
let spaces = spaces.len(); // now usize — totally valid
Section 04

Strings

Rust has two string types, which confuses most beginners. Think of &str as a read-only view and String as a mutable, owned buffer.

TypeScript — one type
const name: string = "Alice";
let greeting = `Hello, ${name}!`;
greeting += " How are you?";
Rust — two types
let name: &str = "Alice";   // string slice (static)
let mut greeting = String::from("Hello, ");
greeting.push_str(name);
greeting.push('!');       // single char

// format! = template literal
let msg = format!("Hello, {}!", name);
&strString
SizeFixed (known at compile time)Dynamic (heap allocated)
MutabilityImmutable viewMutable, owned
WhereStack / static memoryHeap
Analogyconst s = "hi"let s = new String("hi")
Convert to others.to_string()&s or s.as_str()
💡
Rule of thumb: Use &str for function parameters (it accepts both). Use String when you need to own or modify the string data.
Common string operations
let s = String::from("Hello, World!");

// Length
s.len();                    // 13

// Contains / starts_with / ends_with
s.contains("World");       // true
s.starts_with("Hello");    // true

// Split and collect
let words: Vec<&str> = s.split(', ').collect();

// Replace
let new_s = s.replace("World", "Rust");

// Trim, uppercase, lowercase
"  hello  ".trim();       // "hello"
"hello".to_uppercase();   // "HELLO"

// Parse string to number
let n: i32 = "42".parse().unwrap();

// Number to string
let s = 42.to_string();
Section 05

Functions

Functions in Rust use fn instead of function. Parameter and return types are required — no implicit any.

TypeScript
function add(a: number, b: number): number {
  return a + b;
}

// Arrow function
const double = (x: number): number => x * 2;
Rust
fn add(a: i32, b: i32) -> i32 {
    a + b  // no semicolon = return value
}

// Closure (lambda)
let double = |x: i32| x * 2;
🦀
Implicit return: In Rust, the last expression without a semicolon is the return value. Adding a semicolon turns it into a statement (returning ()). This is a common source of bugs for beginners!
Function examples
// Basic function
fn greet(name: &str) -> String {
    format!("Hello, {}!", name)  // no semicolon = returned
}

// Multiple return values via tuple
fn min_max(nums: &[i32]) -> (i32, i32) {
    let min = *nums.iter().min().unwrap();
    let max = *nums.iter().max().unwrap();
    (min, max)
}
let (lo, hi) = min_max(&[3, 1, 4, 1, 5]);

// No return value (unit type)
fn print_hello() {   // -> () is implicit
    println!("Hello!");
}

// Early return
fn divide(a: f64, b: f64) -> f64 {
    if b == 0.0 {
        return 0.0;  // explicit early return
    }
    a / b
}
Section 06

Control Flow

if and loop are similar to TypeScript, but Rust also has match — a supercharged version of switch that's exhaustive and can destructure values.

TypeScript — if/else
const x = 5;
if (x > 0) {
  console.log("positive");
} else if (x < 0) {
  console.log("negative");
} else {
  console.log("zero");
}

// Ternary
const label = x > 0 ? "pos" : "neg";
Rust — if/else
let x = 5;
if x > 0 {             // no parentheses!
    println!("positive");
} else if x < 0 {
    println!("negative");
} else {
    println!("zero");
}

// if is an expression (no ternary needed)
let label = if x > 0 { "pos" } else { "neg" };

Loops

TypeScript
// for...of
for (const n of [1, 2, 3]) {
  console.log(n);
}

// while
let i = 0;
while (i < 5) { i++; }

// range-like
for (let i = 0; i < 5; i++) { }
Rust
// for...in
for n in [1, 2, 3] {
    println!("{}", n);
}

// while
let mut i = 0;
while i < 5 { i += 1; } // no i++

// range (0..5 = exclusive, 0..=5 = inclusive)
for i in 0..5 { println!("{}", i); }

// loop = infinite loop (use break to exit)
let result = loop {
    break 42;  // loop can return a value!
};

match — Rust's supercharged switch

TypeScript — switch
switch (day) {
  case "Mon":
    console.log("Start");
    break;
  case "Fri":
    console.log("End");
    break;
  default:
    console.log("Midweek");
}
Rust — match
match day {
    "Mon" => println!("Start"),
    "Fri" => println!("End"),
    _     => println!("Midweek"),  // _ = default
}

// match is an expression
let label = match score {
    90..=100 => "A",  // ranges!
    80..=89  => "B",
    _         => "C",
};
⚠️
match is exhaustive — you must handle every possible case or the compiler will error. This prevents forgotten edge cases that TypeScript's switch lets slip through.
Section 07

Structs

Structs are Rust's version of TypeScript interfaces and classes combined. They define data shape. Methods are added separately in an impl block.

TypeScript — interface + class
interface User {
  name: string;
  age: number;
  active: boolean;
}

class User {
  constructor(
    public name: string,
    public age: number
  ) {}

  greet() {
    return `Hi, I'm ${this.name}`;
  }
}
const u = new User("Alice", 30);
Rust — struct + impl
struct User {
    name: String,
    age: u32,
    active: bool,
}

impl User {
    // constructor pattern
    fn new(name: &str, age: u32) -> Self {
        User { name: name.to_string(),
                age, active: true }
    }
    // method (&self = read-only)
    fn greet(&self) -> String {
        format!("Hi, I'm {}", self.name)
    }
}
let u = User::new("Alice", 30);
Struct features
// Create instance (all fields required)
let user1 = User {
    name: String::from("Alice"),
    age: 30,
    active: true,
};

// Struct update syntax (like JS spread)
let user2 = User {
    name: String::from("Bob"),
    ..user1   // copy remaining fields from user1
};

// Destructuring
let User { name, age, .. } = user2;

// Tuple structs (named tuples)
struct Point(f64, f64);
let p = Point(1.0, 2.0);
println!("{}", p.0);  // 1.0

// impl methods: &self = read, &mut self = write, self = consume
impl User {
    fn deactivate(&mut self) {
        self.active = false;
    }
}
Section 08

Enums

Rust enums are far more powerful than TypeScript's. Each variant can carry different data — similar to TypeScript discriminated unions but built into the language and exhaustively checked.

TypeScript — discriminated union
type Shape =
  | { kind: "circle"; radius: number }
  | { kind: "rect"; w: number; h: number }
  | { kind: "triangle" };

function area(s: Shape): number {
  switch (s.kind) {
    case "circle": return Math.PI * s.radius ** 2;
    case "rect":   return s.w * s.h;
    default:        return 0;
  }
}
Rust — enum
enum Shape {
    Circle { radius: f64 },
    Rect { w: f64, h: f64 },
    Triangle,
}

fn area(s: Shape) -> f64 {
    match s {
        Shape::Circle { radius } =>
            std::f64::consts::PI * radius * radius,
        Shape::Rect { w, h } => w * h,
        Shape::Triangle => 0.0,
    }  // exhaustive — compiler enforces all cases
}
Creating and using enum values
let s1 = Shape::Circle { radius: 5.0 };
let s2 = Shape::Rect { w: 3.0, h: 4.0 };
let s3 = Shape::Triangle;

println!("Area: {}", area(s1));  // ~78.5

// Enum with impl
impl Shape {
    fn is_round(&self) -> bool {
        matches!(self, Shape::Circle { .. })
    }
}
Section 09

Option<T> — No More Null

Rust has no null or undefined. Instead, values that might be absent are wrapped in Option<T> — an enum with two variants: Some(value) or None.

TypeScript — nullable
function findUser(id: number): User | null {
  return db.find(u => u.id === id) ?? null;
}

const user = findUser(1);
if (user !== null) {
  console.log(user.name);
}

// Optional chaining
const name = user?.name ?? "unknown";
Rust — Option<T>
fn find_user(id: u32) -> Option<User> {
    db.iter().find(|u| u.id == id).cloned()
}

let user = find_user(1);
if let Some(u) = user {
    println!("{}", u.name);
}

// unwrap_or = ?? "fallback"
let name = find_user(1)
    .map(|u| u.name)
    .unwrap_or(String::from("unknown"));

Working with Option

Option methods cheatsheet
let maybe: Option<i32> = Some(42);
let nothing: Option<i32> = None;

// unwrap (panics if None — use only when certain)
maybe.unwrap();              // 42

// unwrap_or (like ?? in TypeScript)
nothing.unwrap_or(0);        // 0
nothing.unwrap_or_else(|| expensive_default());

// map (transform the inner value if Some)
maybe.map(|x| x * 2);       // Some(84)

// and_then (flatMap — chain Options)
maybe.and_then(|x| if x > 0 { Some(x) } else { None });

// is_some / is_none
maybe.is_some();             // true
nothing.is_none();           // true

// match pattern
match maybe {
    Some(v) => println!("Got: {}", v),
    None    => println!("Nothing"),
}

// if let (concise single-branch match)
if let Some(v) = maybe {
    println!("Got: {}", v);
}
Section 10

Result<T, E> — Explicit Errors

Rust replaces exceptions with Result<T, E>Ok(value) for success, Err(error) for failure. Errors become part of the type signature, so you can't accidentally ignore them.

TypeScript — try/catch
async function readFile(path: string): Promise<string> {
  try {
    return await fs.readFile(path, "utf8");
  } catch (err) {
    throw new Error(`Failed: ${err}`);
  }
}
Rust — Result
use std::fs;
use std::io;

fn read_file(path: &str) -> Result<String, io::Error> {
    let content = fs::read_to_string(path)?; // ? = propagate error
    Ok(content)
}
🦀
The ? operator is Rust's equivalent of await unwrapping — it extracts the Ok value or immediately returns the Err to the caller. It's how you propagate errors upward without nesting.
Result methods cheatsheet
let ok: Result<i32, String> = Ok(42);
let err: Result<i32, String> = Err(String::from("oops"));

// unwrap (panics on Err)
ok.unwrap();               // 42

// unwrap_or / unwrap_or_else
err.unwrap_or(0);          // 0

// map / map_err (transform Ok or Err side)
ok.map(|x| x * 2);        // Ok(84)

// match pattern
match ok {
    Ok(v)  => println!("Value: {}", v),
    Err(e) => println!("Error: {}", e),
}

// ? operator — propagate error to caller
fn do_work() -> Result<(), String> {
    let val = risky_operation()?;  // returns Err early if fails
    println!("Got: {}", val);
    Ok(())
}
Section 11

Ownership

Ownership is Rust's most unique concept — and the hardest for newcomers. It's the system that allows Rust to manage memory safely without a garbage collector. There's no equivalent in TypeScript.

ℹ️
Three Rules of Ownership:
  1. Each value has exactly one owner.
  2. There can only be one owner at a time.
  3. When the owner goes out of scope, the value is dropped (freed).
Stack vs Heap — where data lives
i32: 42 bool: true f64: 3.14 ← STACK (fast, fixed size, copied on move)
String "hello" Vec<i32> Box<T> ← HEAP (dynamic size, ownership tracked)
Move semantics (heap types)
// Stack types (Copy trait) — just copied, no ownership transfer
let x: i32 = 5;
let y = x;    // x is copied
println!("{}", x);  // still valid!

// Heap types (String, Vec, etc.) — ownership moves
let s1 = String::from("hello");
let s2 = s1;         // s1 is MOVED into s2
// println!("{}", s1); // COMPILE ERROR: s1 no longer owns the data
println!("{}", s2);  // s2 is the owner now

// To keep both, explicitly clone (deep copy)
let s3 = s2.clone();  // both s2 and s3 are valid
println!("{} {}", s2, s3);
Ownership through functions
fn take_ownership(s: String) -> String {
    println!("{}", s);
    s  // return ownership back to caller
}

let s = String::from("hello");
let s = take_ownership(s);  // ownership moved in AND returned
println!("{}", s);           // valid again

// Better: pass a reference instead (next section!)
Section 12

Borrowing & References

Instead of transferring ownership, you can borrow a value with & (read-only) or &mut (mutable). Borrowing lets you use a value without taking ownership.

TypeScript — always reference
function printLength(s: string): void {
  console.log(s.length);
  // caller still owns s
}

const s = "hello";
printLength(s);
console.log(s); // still accessible
Rust — explicit borrowing
fn print_length(s: &String) {  // & = borrow
    println!("{}", s.len());
    // s dropped here, but not the data
}

let s = String::from("hello");
print_length(&s);   // pass a reference
println!("{}", s);  // still valid!
Borrowing rules
let mut s = String::from("hello");

// RULE 1: Many immutable references at once is OK
let r1 = &s;
let r2 = &s;
println!("{} {}", r1, r2);  // fine

// RULE 2: Only ONE mutable reference at a time
let r3 = &mut s;
// let r4 = &mut s;  // COMPILE ERROR: can't have two &mut
r3.push_str(" world");

// RULE 3: Can't mix mutable + immutable references
let r5 = &s;
// let r6 = &mut s;  // COMPILE ERROR while r5 exists
println!("{}", r5);

// Mutable reference after immutable references are done
let r7 = &mut s;  // OK now — r5 no longer used
r7.push_str("!");
💡
Mental model: References are like read locks (&) and write locks (&mut). Many readers at once is fine. A writer requires exclusive access. The compiler enforces this at compile time — no runtime deadlocks possible.
Section 13

Collections

Rust's standard collections: Vec<T> (like JS Array), HashMap<K,V> (like JS Map/Object), and HashSet<T> (like JS Set).

TypeScript — Array
const nums: number[] = [1, 2, 3];
nums.push(4);
nums[0];                 // 1
nums.len;               // .length
const doubled = nums.map(x => x * 2);
const evens = nums.filter(x => x % 2 === 0);
const sum = nums.reduce((a, b) => a + b, 0);
Rust — Vec<T>
let mut nums: Vec<i32> = vec![1, 2, 3];
nums.push(4);
nums[0];                 // 1
nums.len();              // method call
let doubled: Vec<_> = nums.iter().map(|x| x * 2).collect();
let evens: Vec<_> = nums.iter().filter(|&&x| x % 2 == 0).collect();
let sum: i32 = nums.iter().sum();
TypeScript — Map
const map = new Map<string, number>();
map.set("a", 1);
map.get("a");          // 1 | undefined
map.has("a");          // true
map.delete("a");
Rust — HashMap
use std::collections::HashMap;
let mut map: HashMap<String, i32> = HashMap::new();
map.insert(String::from("a"), 1);
map.get("a");           // Option<&i32>
map.contains_key("a"); // true
map.remove("a");
Vec and HashMap patterns
// Vec — common patterns
let mut v = vec![3, 1, 4, 1, 5];
v.sort();                  // in-place sort
v.dedup();                 // remove consecutive duplicates
v.retain(|&x| x > 2);   // keep elements matching predicate
v.iter().enumerate()       // like Array.entries()
  .for_each(|(i, val)| println!("{}: {}", i, val));

// HashMap — or_insert pattern (great for counting)
let mut counts: HashMap<char, u32> = HashMap::new();
for c in "hello".chars() {
    let count = counts.entry(c).or_insert(0);
    *count += 1;
}
// counts = {'h':1, 'e':1, 'l':2, 'o':1}
Section 14

Closures & Iterators

Rust closures use |args| syntax (pipe characters instead of arrow). Iterators are lazy — chaining .map().filter() doesn't allocate until you .collect().

TypeScript
const nums = [1, 2, 3, 4, 5];

const result = nums
  .filter(x => x % 2 === 0)
  .map(x => x * x)
  .reduce((acc, x) => acc + x, 0);
// result = 20
Rust
let nums = vec![1, 2, 3, 4, 5];

let result: i32 = nums.iter()
    .filter(|&&x| x % 2 == 0)
    .map(|&x| x * x)
    .sum();
// result = 20
Closure capture modes
let threshold = 10;   // captured from outer scope

// Captures by reference (borrows threshold)
let filter = |x: &i32| *x > threshold;

// move closure — takes ownership of captured vars
let add_n = move |x: i32| x + threshold;
// threshold still usable since i32 is Copy

// Iterator methods
let v = vec![1, 2, 3];
v.iter().any(|&x| x > 2);       // true (like .some())
v.iter().all(|&x| x > 0);       // true (like .every())
v.iter().find(|&&x| x == 2);    // Some(&2)
v.iter().position(|&x| x == 2); // Some(1)
v.iter().count();               // 3
v.iter().max();                  // Some(&3)
v.iter().min();                  // Some(&1)
v.iter().rev();                  // reversed iterator
v.iter().zip(["a", "b", "c"]); // like zip()
v.iter().flat_map(|&x| 0..x);  // flatMap
v.iter().take(2);               // first 2
v.iter().skip(1);               // skip first
Section 15

Traits

Traits are Rust's equivalent of TypeScript interfaces — but they define behavior (methods) rather than shape. You can implement a trait for any type, even types you didn't create.

TypeScript — interface
interface Greetable {
  greet(): string;
}

class Dog implements Greetable {
  greet() { return "Woof!"; }
}

function sayHi(g: Greetable) {
  console.log(g.greet());
}
Rust — trait
trait Greetable {
    fn greet(&self) -> String;
}

struct Dog;
impl Greetable for Dog {
    fn greet(&self) -> String { "Woof!".into() }
}

fn say_hi(g: &impl Greetable) {
    println!("{}", g.greet());
}
Common derived traits
// #[derive] auto-implements common traits
#[derive(Debug, Clone, PartialEq)]
struct Point {
    x: f64,
    y: f64,
}

let p = Point { x: 1.0, y: 2.0 };

// Debug — like console.log(JSON.stringify(p))
println!("{:?}", p);   // Point { x: 1.0, y: 2.0 }
println!("{:#?}", p);  // pretty-printed

// Clone — explicit deep copy
let p2 = p.clone();

// PartialEq — enables ==
let same = p == p2;    // true
TypeScriptRust traitPurpose
Auto JSON.stringifyDebugFormat for debugging
... spreadCloneExplicit deep copy
==PartialEqEquality comparison
< >PartialOrdOrdering
.toString()DisplayUser-facing formatting
Iterable protocolIteratorCustom iteration
DefaultZero-value constructor
Section 16

Modules & Cargo

Rust uses a module system similar to ES modules, but everything is private by default. Use pub to expose things, and use to bring them into scope.

TypeScript
// math.ts
export function add(a: number, b: number) {
  return a + b;
}

// main.ts
import { add } from "./math";
console.log(add(1, 2));
Rust
// src/math.rs
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

// src/main.rs
mod math;          // declare module
use math::add;     // bring into scope
println!("{}", add(1, 2));
Module patterns
// Inline module
mod utils {
    pub fn helper() -> &'static str { "help" }

    fn private() {}  // private by default
}

// Use paths
use std::collections::HashMap;  // std library
use utils::helper;

// Multiple imports
use std::collections::{HashMap, HashSet, BTreeMap};

// Rename
use std::io::Result as IoResult;

// Re-export
pub use self::utils::helper;

// File structure for larger projects:
// src/
//   main.rs          ← binary entry
//   lib.rs           ← library root
//   auth/
//     mod.rs         ← declares the auth module
//     login.rs
//     session.rs

Popular Crates (packages)

NeedCratenpm equivalent
Async runtimetokioNode.js built-in
HTTP clientreqwestaxios / fetch
HTTP serveraxum, actix-webexpress / fastify
JSONserde_jsonJSON.parse/stringify
Serializeserdezod / class-transformer
CLI argsclapcommander / yargs
Loggingtracingwinston / pino
Error handlinganyhow, thiserror
Database (ORM)sqlx, dieselprisma / drizzle
Date/timechronodate-fns / luxon
UUIDuuiduuid
RegexregexBuilt-in RegExp
Reference

Quick Cheatsheet

TypeScript → Rust at a glance.

TypeScript → Rust syntax map
// Variables
let x = 5let x = 5;          // immutable
let x = 5; x = 6let mut x = 5; x = 6;
const X = 5const X: i32 = 5;   // needs type

// Functions
(a: number): number   → (a: i32) -> i32
x => x * 2            → |x| x * 2

// Types
number                 → i32 / f64
string                 → String / &str
boolean                → bool
T[]                    → Vec<T>
[T, U]                 → (T, U)
Map<K, V>             → HashMap<K, V>
T | null               → Option<T>
Promise<T>            → async fn -> T

// Null handling
x ?? "default"         → x.unwrap_or("default")
x?.method()            → x.map(|v| v.method())
x!                     → x.unwrap()

// Error handling
try { } catch(e) { }   → match result { Ok(v) => .., Err(e) => .. }
throw new Error("msg") → return Err("msg".into())
await fn()             → fn()?              // propagate error

// Control flow
if (x > 0)             → if x > 0           // no parens
switch (x) { }         → match x { }
x > 0 ? "y" : "n"     → if x > 0 { "y" } else { "n" }
for (const v of arr)   → for v in arr
for (let i=0;ifor i in 0..n

// Printing
console.log(x)         → println!("{}", x)
console.log(obj)       → println!("{:?}", obj)   // needs Debug
`Hello ${name}`        → format!("Hello {}", name)

// Struct/class
interface Foo { x: T } → struct Foo { x: T }
class Foo { method() } → impl Foo { fn method(&self) }
implements Interface   → impl Trait for Type
{ ...obj, x: 1 }      → Struct { x: 1, ..obj }

// Imports
import { x } from "m"  → use m::x;
export function foo     → pub fn foo
npm install pkg         → cargo add pkg
What's Next

Next Steps

Now that you understand the basics, here's where to go next.

Beginner Projects to Build

  • CLI tool — use clap for args, read/write files. Great first project.
  • Simple HTTP server — build a REST API with axum + serde_json.
  • Word counter — practice HashMap, file reading, iterators.
  • Mini grep — implement a simplified grep to learn string processing.
  • Todo app CLI — serialization with serde, file persistence.

Topics to Learn Next

  • Lifetimes — when the borrow checker needs explicit hints ('a).
  • Generics — write functions that work for any type: fn foo<T: Display>(x: T)
  • Async / Tokioasync fn and .await for non-blocking I/O.
  • Error handlingthiserror for library errors, anyhow for apps.
  • Smart pointersBox<T>, Rc<T>, Arc<T>, RefCell<T>.
  • WebAssembly — compile Rust to WASM and call it from TypeScript!

Resources

  • The Bookdoc.rust-lang.org/book — the official free Rust book. Read chapters 1-10 first.
  • Rustlings — small exercises to practice each concept (cargo install rustlings).
  • Rust by Exampledoc.rust-lang.org/rust-by-example
  • docs.rs — documentation for every crate on crates.io.
  • crates.io — the npm registry of Rust.
🎯
Best approach: Read The Book chapters 1-10, do Rustlings exercises in parallel, then build one of the beginner CLI projects above. The ownership system clicks once you've seen the compiler errors a few times — trust the process!