Back to campField Journalby Zin
All entries

rust · typescript · javascript

Rust: Zero to Hero, for JavaScript & TypeScript Developers

Rust through a JS/TS lens: ownership, borrowing, enums, traits, iterators, errors, threads and async, every example explained line by line and runnable on the page.

42 min read0 likes

Rust: သုညကနေ Hero ဆီ၊ JavaScript နဲ့ TypeScript Developer တွေအတွက်

JS/TS မျက်လုံးနဲ့ ကြည့်တဲ့ Rust — ownership၊ borrowing၊ enum၊ trait၊ iterator၊ error၊ thread နဲ့ async၊ ဥပမာတိုင်းကို တစ်ကြောင်းချင်း ရှင်းပြထားပြီး စာမျက်နှာပေါ်မှာတင် run လို့ရပါတယ်။

ဖတ်ချိန် 40 မိနစ်0 likes

If you write JavaScript or TypeScript for a living, Rust can look like a language from another planet: lifetimes, borrow checkers, &mut, compiler errors that run for twenty lines. Underneath, though, a lot of it will feel familiar. It has closures, iterators with map and filter, async/await, a package manager that works like npm, and a type system that does what TypeScript's does, then goes further and also proves your program can't touch freed memory or race on shared data.

This guide takes you from zero to the point where you can read real Rust code and write a small program of your own. Each section puts a Rust idea next to the JS/TS one you already know. Every example has line-by-line notes under it: tap or hover a highlighted line to read what it does. The Rust examples are live. Edit them and press Run (or ⌘/Ctrl + Enter) to compile and run them on the official Rust Playground, right here on the page.

Why Rust, for a JS/TS developer

Rust began as a side project of Graydon Hoare at Mozilla in 2006, was sponsored by Mozilla from 2009, and reached 1.0 in May 2015. Since 2021 it has been looked after by the independent Rust Foundation, whose members include AWS, Google, Microsoft and Huawei. Developers have voted it the most admired language in Stack Overflow's survey every year since 2016.

It sets out to give you two things that used to be a trade-off: the speed and control of C and C++, and the memory safety of a garbage-collected language like JavaScript, without the garbage collector. Here is how that changes things compared with what you know:

  • No garbage collector, no runtime. V8 pauses now and then to find and free unused objects. Rust frees each value at a point the compiler decides ahead of time, so there are no pauses and memory use stays flat and predictable.
  • Types are checked, then compiled away. Like TypeScript, Rust checks types before the program runs. Unlike TypeScript, there is no any, no as unknown as, and the types decide the machine code that gets generated.
  • Ownership replaces "everything is a shared reference." In JS, any number of variables can point at the same object and change it. In Rust, each value has one owner, and borrowing rules decide who may read or write it at a time. Those rules are what remove whole classes of bugs.
  • Errors are values. There are no exceptions to forget to catch. A function that can fail returns a Result, and the compiler makes you deal with it.
  • No null or undefined. A value that might be missing is an Option, and you can't use it until you've handled the "missing" case.
  • One binary out. cargo build --release produces a single native executable with no Node, no node_modules and no interpreter needed where it runs.

How Rust works: the architecture

Node reads your JavaScript at run time and V8 compiles it on the fly, re-optimising hot functions as it learns their types. Rust does all of that work before the program exists, in the rustc compiler. Knowing the stages makes the compiler's error messages much easier to read:

  1. Parsing and macro expansion. Source text becomes a syntax tree. Macros such as println! and vec! (the ! marks a macro) are expanded into ordinary code here, and #[derive(...)] attributes generate code for your types.
  2. Name resolution and HIR. Names are matched to their definitions and the tree is lowered to the High-level IR, a desugared form (a for loop becomes a loop with a match, for example).
  3. Type checking and trait resolution. Types are inferred and checked, and every method call is matched to a concrete implementation. This is where TypeScript would stop.
  4. MIR and the borrow checker. The program is lowered again to the Mid-level IR, a control-flow graph of simple statements. The borrow checker walks this graph and proves that every reference is valid and that no value is written while someone else is reading it. Ownership errors come from here.
  5. Monomorphisation. Generic code (fn largest<T>) is copied once for each concrete type it is used with, so largest::<i32> and largest::<f64> become two specialised functions with no runtime type checks. That is why generics in Rust cost nothing at run time.
  6. LLVM and the linker. MIR is translated to LLVM IR, the same optimiser back end used by Clang and Swift, which produces machine code for your target (x86-64, ARM, WebAssembly…). The linker joins your crates into one binary.

Some other pieces of the design are worth knowing up front:

  • Zero-cost abstractions. Iterators, closures, Option and generics compile down to the same machine code you would have written by hand with loops and pointers. You are not charged at run time for writing at a higher level.
  • Crates and Cargo. A crate is one compilation unit, a library or a binary (think one npm package). Cargo builds, tests, documents and publishes crates, and crates.io is the registry.
  • std, alloc and core. The standard library comes in layers. core works anywhere, even on a microcontroller with no operating system. alloc adds heap types like Vec and String. std adds files, threads and networking. Embedded and kernel code uses only the lower layers.
  • Editions. Every few years (2015, 2018, 2021, 2024) an edition bundles small breaking changes to the syntax. Each crate picks its edition in Cargo.toml, and crates of different editions link together freely, so the ecosystem never splits the way Python 2 and 3 did.
  • Async without a built-in runtime. The language provides async/await and the Future trait, but no event loop. You choose a runtime library; Tokio is the standard one for servers. Rust's futures do nothing until polled, unlike JS promises, which start running as soon as they are created.
  • unsafe as a fenced area. A small set of operations the compiler can't check (raw pointers, calling C) must be written inside unsafe { }. Safe Rust is built on top of small, reviewed unsafe cores inside the standard library.

Installing Rust

Install with rustup, the official toolchain manager (the nvm of Rust): on macOS or Linux run the one-liner from rustup.rs, and on Windows download rustup-init.exe from the same site. It installs the compiler rustc, the package manager cargo, the formatter and the linter. Then the commands you'll use every day:

sh
rustup update             # update to the newest stable Rustcargo new hello           # a new project with Cargo.toml and src/main.rscd hellocargo run                 # build in debug mode and runcargo check               # type-check and borrow-check only, much fastercargo test                # run every #[test] functioncargo clippy              # the linter, like ESLintcargo fmt                 # the formatter, like Prettiercargo add serde --features derive   # like npm install serdecargo build --release     # an optimised binary in target/release/

Line by line

  1. Rust ships a new stable version every six weeks. rustup update moves you to it; old code keeps compiling, because Rust promises stability within an edition.
  2. Creates a folder with Cargo.toml (your package.json), src/main.rs (the entry point), and a git repository. Use cargo new --lib name for a library.
  3. Compiles and runs. The first build compiles every dependency and is slow; later ones are incremental. Build output goes into target/, which plays the role of dist/ and stays out of git.
  4. Runs everything except code generation. Use it while you write; it is the closest thing to tsc --noEmit. Editors with rust-analyzer run it for you on save.
  5. Finds and runs tests in your code and in tests/. There is no Jest or Vitest to install: testing is built into the language and Cargo.
  6. Clippy has hundreds of lints that point out non-idiomatic or slow code and usually suggest the fix. Treat it like a reviewer.
  7. rustfmt formats the code in one official style. Almost every Rust project uses it, so there are no style debates.
  8. Adds a dependency to Cargo.toml. A feature switches on optional parts of a crate; serde's derive feature gives you #[derive(Serialize, Deserialize)].
  9. Builds with full optimisations. Debug builds can be ten times or more slower than release builds, so benchmark only release builds.

A project's manifest is a TOML file. Here it is next to what you know:

toml
[package]name = "hello"version = "0.1.0"edition = "2024" [dependencies]serde = { version = "1.0", features = ["derive"] }tokio = { version = "1", features = ["full"] } [dev-dependencies]pretty_assertions = "1"

Line by line

  1. The [package] table is the top of your package.json: name and version (Cargo uses semver as npm does).
  2. The edition this crate is written in. New projects get 2024. It changes a few syntax rules, never which crates you can use.
  3. Runtime dependencies, like dependencies in package.json. Exact versions are pinned in Cargo.lock, which plays the role of package-lock.json: commit it for applications.
  4. "1.0" means "1.0 or newer, below 2.0", the same as ^1.0 in npm. Features are chosen per dependency, so you compile only what you use.
  5. Dependencies used only by tests, examples and benchmarks: devDependencies.

Hello, Rust

Every program starts at fn main. Press Run to compile and run it, then try changing the name.

rustEditable: change it and press Run

Tab indents · ⌘/Ctrl + Enter runs · Esc leaves the editor

Line by line

  1. fn declares a function, and main is where a binary starts. There is no top-level code as in a JS module: everything runs from main. Blocks use braces, and indentation is four spaces by convention.
  2. let creates a variable. Its type, &str (a borrowed string), is inferred, as TypeScript infers const name = "Rust". Statements end with ;.
  3. An integer literal. With nothing else to go on, Rust picks i32, a 32-bit signed integer. There is no single number type: integers and floats are separate.
  4. println! is a macro (the !), not a function: it checks the format string at compile time. {name} inlines a variable, like ${name} in a template literal. A typo in the name is a compile error, not undefined.
  5. Empty {} placeholders are filled by the extra arguments, in order. {} uses the type's user-facing Display format.
  6. Methods are called with a dot, as in JS. to_uppercase returns a new String, a growable string on the heap that this variable owns.
  7. Placeholders can be mixed. len() returns the length in bytes (here the same as letters, because the text is ASCII).

Variables, mutability and shadowing

In TypeScript const stops you reassigning a variable, but the object it points at can still be changed. Rust is stricter and simpler: a binding is immutable all the way down unless you write mut.

ts
const score = 10;      // can't be reassignedlet lives = 3;         // can belives -= 1;const user = { name: 'Ada' };user.name = 'Grace';   // allowed: const fixes the binding, not the object

Line by line

  1. In JS you choose between const and let; most style guides prefer const.
  2. This is the surprise in JS: const does not make the object immutable. You would need Object.freeze or Readonly<T> for that, and even those are shallow.
rustEditable: change it and press Run

Tab indents · ⌘/Ctrl + Enter runs · Esc leaves the editor

Line by line

  1. A plain let is immutable, like const, but deeply: if score were a struct, none of its fields could change either.
  2. Remove the // and press Run to see the compiler refuse. Rust's error messages are famously helpful: this one points at both lines and suggests adding mut.
  3. let mut makes the binding mutable, like let in JS. Mutability is explicit, so a reader can see at a glance which values change.
  4. Shadowing: a second let input creates a new variable with the same name, here with a different type. It is common for "parse then forget the text" steps. parse() returns a Result, and unwrap() takes the value out (or crashes if parsing failed; more on that later).
  5. const is a compile-time constant. It needs an explicit type, is written in SCREAMING_CASE, and is inlined wherever it is used. It is closer to a value you would put in a config file than to JS const.

Types: numbers, text, tuples and arrays

JavaScript has one number (a 64-bit float) and bigint. Rust has a type for each size, so you choose exactly how much memory a value takes and whether it can be negative.

rustEditable: change it and press Run

Tab indents · ⌘/Ctrl + Enter runs · Esc leaves the editor

Line by line

  1. Signed integers are i8, i16, i32, i64 and i128; isize is the size of a pointer. i32 is the default.
  2. Unsigned integers (u8 … u128, usize) can't be negative. A u8 holds 0 to 255, one byte. usize is the type of lengths and indexes.
  3. Floats are f32 and f64; f64 is the same as a JS number. A suffix such as _f64 sets a literal's type, and _ can separate digits anywhere (1_000_000).
  4. A char is one Unicode scalar value, four bytes, written in single quotes, so a Burmese letter is one char. Strings use double quotes.
  5. 255 + 1 doesn't fit in a u8. In a debug build plain + would panic (stop the program) on overflow rather than silently wrap. When overflow is possible, say what you want: checked_add returns None on overflow, wrapping_add wraps around to 0, and saturating_add stops at the maximum.
  6. Integer division truncates, so 7 / 2 is 3; with floats it is 3.5. In JS both give 3.5.
  7. Rust never converts number types for you, not even i32 to f64. as converts explicitly. a * c without it would be a compile error: mixing types is always a decision you write down.
  8. A tuple groups values of different types, like a TS tuple [number, number]. Destructure it with let (x, y), or read a field by position with .0, .1.
  9. An array has a fixed length that is part of its type ([&str; 3]) and lives on the stack. Indexing past the end panics instead of returning undefined. For a list that grows, use Vec, shown later.

Functions, and everything is an expression

The biggest change in day-to-day style: in Rust almost everything is an expression that produces a value, including if, match and whole blocks. The last expression of a block, written without a semicolon, is its value.

rustEditable: change it and press Run

Tab indents · ⌘/Ctrl + Enter runs · Esc leaves the editor

Line by line

  1. Parameter types are required, as in strict TypeScript, and come after the name. -> i32 is the return type. Unlike TS, Rust never infers a function's signature: signatures are the contract, and inference works only inside the body.
  2. No return and no semicolon: the last expression is returned. Adding a ; would turn it into a statement whose value is () (the empty "unit" type, Rust's void), and the compiler would complain that the function returns nothing. return exists for leaving early.
  3. &'static str is a string slice that lives for the whole program: string literals are stored in the binary itself, so they are always valid.
  4. if is an expression, so it works as JS's ternary operator does. Every branch must have the same type, and the condition must be a real bool: there is no truthiness, so if 1 { } doesn't compile.
  5. A block in braces is an expression too. The temporary w and h exist only inside it, and its value is w * h. This is a tidy way to compute a value in a few steps without leaking helper variables.

Control flow: loops and match

rustEditable: change it and press Run

Tab indents · ⌘/Ctrl + Enter runs · Esc leaves the editor

Line by line

  1. loop repeats forever until a break. It replaces while (true), and because it is an expression it can produce a value.
  2. break count leaves the loop and hands count out as the loop's value, so found becomes 8. In JS you would need a variable declared outside the loop.
  3. while works as in JS, without parentheses around the condition. print! is println! without the newline.
  4. for loops over anything iterable. 1..=3 is an inclusive range (1, 2, 3) and 1..3 excludes the end (1, 2). There is no C-style for (let i = 0; i < n; i++), and you rarely miss it.
  5. .iter().enumerate() yields (index, item) pairs, destructured in the loop head, like for (const [i, lang] of langs.entries()).
  6. match is switch with superpowers: it is an expression, arms can match ranges, tuples, enums and conditions, and there is no fall-through, so no break to forget.
  7. _ matches anything. match must be exhaustive: remove this arm and the compiler will tell you which numbers are not covered. That check is what makes match on enums so useful later.

Ownership: the idea everything else rests on

Here is the heart of Rust, and the part with no JS equivalent. First, recall how JavaScript treats objects:

ts
const a = { tags: ['ts'] };const b = a;           // b and a point at the same objectb.tags.push('rust');console.log(a.tags);   // ['ts', 'rust']: changed through b

Line by line

  1. Assigning an object copies the reference. Now two names share one object, and the garbage collector frees it only when neither can reach it any more.
  2. A change through one name is visible through the other. This sharing is convenient, and it is also the root of many bugs: a function you call might keep a reference and change your data later.

Rust replaces this with three rules that the compiler checks:

  1. Every value has exactly one owner, a variable.
  2. When the owner goes out of scope, the value is dropped (its memory is freed) right there. No garbage collector is needed.
  3. Assigning or passing a heap value moves ownership to the new place. The old name can no longer be used.
rustEditable: change it and press Run

Tab indents · ⌘/Ctrl + Enter runs · Esc leaves the editor

Line by line

  1. String::from puts the text on the heap and makes a its owner. :: calls a function that belongs to a type, like a static method.
  2. This is a move, not a shared reference. Ownership passes to b and a becomes unusable. Only the pointer, length and capacity are copied; the text itself stays where it is.
  3. Uncomment this line and Run: the compiler rejects using a after the move. In JS this would silently share the object; in Rust there is never more than one owner to free it.
  4. .clone() makes an explicit deep copy, so b and c own separate strings. Clones are visible in the code, so you can see where copying costs you.
  5. Small fixed-size types (integers, floats, bool, char, and tuples of them) are Copy: assigning them copies the bits and both names stay usable. Moves only matter for values that own something, like heap memory.
  6. Passing a String to a function moves it into the parameter s. When take returns, s is dropped and the memory freed. Using c after this line would be an error.
  7. Returning a value moves ownership out to the caller, so give creates a string and d becomes its owner.
  8. At the closing brace b and d go out of scope and their strings are freed, in the reverse order they were created. This is deterministic: you know exactly when memory is released (and when files and sockets are closed, because those are freed the same way).

Borrowing: references without taking ownership

Moving everything into functions would be tiring. Instead you borrow: &value gives a function a reference to read, and &mut value gives it a reference to change. The borrow checker enforces one rule: at any moment you can have either any number of shared & references or exactly one &mut reference, never both.

rustEditable: change it and press Run

Tab indents · ⌘/Ctrl + Enter runs · Esc leaves the editor

Line by line

  1. &String is a shared reference: length can read the string but not change it, and does not own it, so nothing is freed when it returns. (Idiomatic code takes &str here; see the next section.)
  2. &mut String is a mutable reference: shout may change the caller's string in place.
  3. &word lends the string out for the duration of the call. Afterwards word is still ours.
  4. To lend mutably, both the variable (let mut word) and the borrow (&mut word) must say so. A reader can see at the call site which functions might change their data, which JS never tells you.
  5. Any number of shared borrows can exist together, because readers can't disturb each other.
  6. A mutable borrow is allowed here because r1 and r2 are not used after the println!. The compiler tracks where each reference is last used (this is called non-lexical lifetimes), not just the block it is declared in.

Now meet the borrow checker saying no. This program does not compile on purpose. Press Run to read the error, then try the fix in the notes.

rustEditable: change it and press Run

Tab indents · ⌘/Ctrl + Enter runs · Esc leaves the editor

Line by line

  1. first borrows the first element of the vector, a pointer into the vector's heap buffer.
  2. push needs &mut list. If the buffer is full, push allocates a bigger one, copies the elements and frees the old buffer. first would then point at freed memory: in C this is a classic use-after-free bug.
  3. Because first is used after the push, the shared borrow and the mutable borrow overlap and the compiler refuses (error E0502). To fix it, copy the value out with let first = list[0]; (an i32 is Copy), or move the println! above the push.

String and &str

Rust has two main string types, and the split confuses everyone at first. String is an owned, growable buffer, like a StringBuilder. &str (a "string slice") is a borrowed view into text that someone else owns: a literal, part of a String, or a whole one. Both are always valid UTF-8.

rustEditable: change it and press Run

Tab indents · ⌘/Ctrl + Enter runs · Esc leaves the editor

Line by line

  1. Take &str for parameters you only read: it accepts literals, Strings and slices alike. Return String when you build new text, because the caller needs to own it.
  2. format! works like println! but returns the String instead of printing it. It is Rust's template literal.
  3. A literal is a &str pointing into the program binary.
  4. A String lives on the heap and can grow. String::from(…), "…".to_string() and "…".to_owned() all make one.
  5. push_str appends in place, which is why owned is mut. Emoji are fine: strings are UTF-8.
  6. &owned borrows the String, and Rust turns &String into &str automatically (deref coercion). That is why &str parameters accept both.
  7. Slicing takes a byte range: 0..5 is "Minga". Slicing in the middle of a multi-byte character panics, so for non-ASCII text, work with chars() or a search instead of fixed numbers.
  8. Burmese characters take three bytes each in UTF-8, so len() (bytes) and chars().count() (Unicode scalar values) differ: 27 and 9. JavaScript's .length counts UTF-16 code units, a third answer. Count what you actually mean.

Lifetimes, briefly

A lifetime is the compiler's name for "how long a reference stays valid." Most of the time it works them out itself. You write them only when a function returns a reference and the compiler can't tell which input it came from.

rustEditable: change it and press Run

Tab indents · ⌘/Ctrl + Enter runs · Esc leaves the editor

Line by line

  1. <'a> declares a lifetime parameter, like a generic. The signature says: the result lives no longer than the shorter-lived of x and y. It doesn't change how long anything lives; it describes the relationship so the caller can be checked.
  2. A struct that holds a reference must say how long that reference lives: an Excerpt can't outlive the text it points into.
  3. b lives only inside this inner block and is freed at its closing brace.
  4. longest may return a reference into b, so keeping that reference past the brace would be an error. .to_string() copies the result into a new owned String, which can live on. Try removing .to_string() to see the error.
  5. split yields &str slices borrowed from text, with no copying. next() takes the first one as an Option and unwrap() takes it out.

Structs and methods

Rust has no classes. Data goes in a struct and behaviour in impl blocks. There is no inheritance; sharing behaviour is the job of traits, covered below.

ts
class Account {  constructor(public owner: string, private balance = 0) {}  deposit(amount: number) { this.balance += amount; }  getBalance() { return this.balance; }}
rustEditable: change it and press Run

Tab indents · ⌘/Ctrl + Enter runs · Esc leaves the editor

Line by line

  1. #[derive] asks the compiler to generate standard trait implementations: Debug for printing with {:?}, Clone for .clone(), and PartialEq for ==. In JS == on objects compares references; in Rust it compares contents, and only if you ask for it.
  2. A struct is a named set of typed fields, like a TS interface that also decides the memory layout. Fields are private to the module unless marked pub.
  3. Money in the smallest unit (cents, pyas) as an unsigned integer: it can't go negative, and there are no float rounding surprises.
  4. An impl block holds the methods of a type. You can have several, even in different files of the same crate.
  5. A function without self is an associated function, Rust's static method. new is only a convention, not a keyword: there are no constructors. Self means the type being implemented.
  6. &mut self means the method borrows the struct mutably, so it can only be called on a mut variable. The signature tells you which methods change the value.
  7. &self borrows it read-only. A field and a method can share a name; acc.balance() calls the method, acc.balance reads the field.
  8. Call associated functions with ::, methods with .. Rust borrows &mut acc automatically for acc.deposit(…).
  9. {:?} (here {acc:?}) prints the Debug form, handy while developing, like console.log of an object. {:#?} pretty-prints it over several lines.
  10. Struct update syntax ..acc.clone() fills the remaining fields from another value, like { ...acc, owner: "Grace" } in JS. We clone because the spread would otherwise move acc's fields.

Enums and pattern matching

If you use discriminated unions in TypeScript, you already understand Rust enums. Each variant can carry its own data, and match makes sure you handle every one.

ts
type Shape =  | { kind: 'circle'; radius: number }  | { kind: 'rect'; w: number; h: number }; function area(s: Shape): number {  switch (s.kind) {    case 'circle': return Math.PI * s.radius ** 2;    case 'rect': return s.w * s.h;  }}

Line by line

  1. TypeScript narrows on the kind tag. Whether a missing case is caught depends on your compiler settings and return type; in Rust it always is.
rustEditable: change it and press Run

Tab indents · ⌘/Ctrl + Enter runs · Esc leaves the editor

Line by line

  1. An enum lists every possible shape of a value. The tag is built in: you never write a kind field yourself.
  2. A variant can carry named fields like a struct, unnamed ones like a tuple (Circle(f64)), or nothing at all.
  3. A variant with no data is like a member of a TS string-literal union.
  4. match destructures each variant and binds its fields as variables. It is exhaustive: add a Triangle variant and every match that forgets it stops compiling. That turns "I forgot to handle the new case" from a production bug into a compile error.
  5. An array of enums. Every element has the same type, Shape, even though the variants carry different data.
  6. {:.2} prints a float with two decimals, like toFixed(2).
  7. match works on any value, not only enums.
  8. A match guard (if …) adds a condition to an arm. x binds the matched value so the guard can use it.

Option instead of null and undefined

Tony Hoare called null references his "billion-dollar mistake." Rust has no null. A value that might be absent has the type Option<T>, an enum with two variants, Some(value) and None, and you must deal with None before you can use the value.

rustEditable: change it and press Run

Tab indents · ⌘/Ctrl + Enter runs · Esc leaves the editor

Line by line

  1. The return type says honestly that there might be no user, as string | undefined would in TS. The difference: an Option<&str> is not a &str, so you can't call string methods on it by accident.
  2. Matching on both variants is the most explicit way to handle it.
  3. if let handles only the case you care about, like if (user !== undefined) with narrowing.
  4. unwrap_or supplies a default, like ?? "guest" in JS.
  5. map transforms the value inside a Some and leaves None alone, like optional chaining user?.length. |n| n.len() is a closure, Rust's arrow function (n) => n.length.
  6. Prints Some(3): the Debug form shows the wrapper.
  7. let … else unwraps or leaves: if the pattern doesn't match, the else block must exit (return, break or panic). It is the Rust version of an early-return guard clause.

Error handling with Result and ?

In JS any function might throw, and nothing in its signature says so. In Rust a function that can fail returns Result<T, E>, an enum of Ok(value) or Err(error). The ? operator propagates errors upward with one character, which gives you the convenience of exceptions with the errors listed in the type.

ts
function parsePort(s: string): number {  const n = Number(s);  if (!Number.isInteger(n) || n < 1 || n > 65535) throw new Error('bad port: ' + s);  return n;}

Line by line

  1. Nothing in parsePort's type tells the caller it can throw, so forgetting try/catch is easy and only shows up at run time.
rustEditable: change it and press Run

Tab indents · ⌘/Ctrl + Enter runs · Esc leaves the editor

Line by line

  1. use brings names into scope, like import. std::fmt is a module path; :: separates its parts.
  2. Your own error type is an enum listing every way this code can fail. One variant wraps the standard library's parse error, the other carries the bad number.
  3. Implementing the Display trait decides how the error prints with {}. write! is format! aimed at a formatter. (In real projects the thiserror crate generates this impl from one attribute per variant.)
  4. A From impl tells ? how to convert a ParseIntError into our ConfigError automatically.
  5. The signature says "a u16 or a ConfigError." Callers must handle both; ignoring a Result gives a compiler warning.
  6. The ? operator: if parse() returned Err, convert the error with From and return it from this function right away; if Ok, take the value out. It replaces a whole try/catch/rethrow. The target type u32 tells parse what to parse into.
  7. Returning an error early is ordinary control flow: Err(…) is just a value.
  8. Success is wrapped in Ok. The as u16 is safe because the range was checked just above.
  9. Matching on the Result handles both outcomes. Notice how the three inputs take different paths. There is also unwrap() and expect("message"), which crash on Err: fine in examples and tests, a code smell in production paths.

Traits and generics

A trait is a set of methods a type promises to have, like a TS interface. The difference: in TS any object with the right shape fits an interface (structural typing), but in Rust a type implements a trait only if someone writes impl Trait for Type (nominal typing). You can implement your traits for other people's types, and other people's traits for yours.

rustEditable: change it and press Run

Tab indents · ⌘/Ctrl + Enter runs · Esc leaves the editor

Line by line

  1. A trait declares method signatures that implementing types must provide.
  2. A trait method can have a default body, like a mixin. Types get it for free and may override it.
  3. impl Describe for Dog is the explicit opt-in. Dog is a unit struct with no fields, which is useful when a type only carries behaviour. .into() converts the &str into the String the signature wants.
  4. Robot overrides the default describe.
  5. &impl Describe means "a reference to any type that implements Describe." It is static dispatch: the compiler generates a separate copy of announce for Dog and for Robot, so the calls are direct and can be inlined.
  6. A generic function with trait bounds: T must be comparable with > (PartialOrd) and copyable (Copy). Like <T extends …> in TS, but bounds list capabilities, and the compiler checks the body uses only those. &[T] is a slice, a borrowed view of any array or Vec.
  7. dyn Describe is dynamic dispatch: a trait object whose method is looked up in a table at run time, like a JS method call. Box puts each value on the heap so that values of different sizes can sit in one list.
  8. Several type parameters, each bound to Display (can be printed with {}). Strings and integers both qualify.
  9. A Vec of trait objects holds different concrete types in one collection, the closest thing to a TS array of Describe. The type annotation tells the compiler to convert each Box<Dog> and Box<Robot> into Box<dyn Describe>.

Collections, iterators and closures

Your favourite array methods are all here, with two differences: you call .iter() to start a chain, and iterators are lazy. Nothing runs until something consumes them (sum, collect, a for loop). The compiler then fuses the whole chain into one loop, with no temporary arrays in between.

ts
const prices = [12, 5, 30, 8];const total = prices.filter((p) => p > 6).map((p) => p * 2).reduce((a, b) => a + b, 0);

Line by line

  1. In JS each step builds a new array: filter makes one, map another, then reduce walks it.
rustEditable: change it and press Run

Tab indents · ⌘/Ctrl + Enter runs · Esc leaves the editor

Line by line

  1. Vec<T> is the growable list, Rust's Array. vec![…] is a macro that builds one from a literal. It must be mut to push.
  2. The same chain as the TS version. .iter() borrows each element without consuming the vector, and .sum() drives the chain. The type annotation on total tells sum what to produce.
  3. The closure receives &&i32 (a reference to the iterator's reference), and the pattern |&&p| strips both. You'll often see |p| **p > 6 or .copied() instead (as in note 5). This is the noisiest part of iterator code, and the compiler tells you when you get it wrong.
  4. collect() gathers an iterator into a collection. It can build many kinds (Vec, HashMap, String…), so you say which with a type annotation. join works as in JS.
  5. .copied() turns &i32 items into plain i32 values. Vec<_> lets the compiler fill in the element type. The closure uses threshold from the surrounding scope, exactly like a JS closure.
  6. HashMap is Map. Keys and values are typed. It is not ordered: iteration order is unspecified, so don't rely on it.
  7. The entry API: get the value for a key, inserting 0 if it is missing, and add to it in place. * dereferences the &mut u32 that or_insert returns. One lookup instead of JS's map.set(k, (map.get(k) ?? 0) + 5).
  8. For stable output, collect the keys and sort them. sort sorts in place; for the reverse order, there is sort_by(|a, b| b.cmp(a)).
  9. get returns an Option, because the key may be missing. stock["plums"] would panic instead.
  10. Nothing has run yet on this line: lengths is just a description of work. max() consumes it and returns Option<usize>, because an empty iterator has no maximum.

Modules, crates and visibility

An ES module is a file. A Rust module is a namespace you declare with mod, either inline or in its own file. Everything is private by default, and pub exports it.

rustEditable: change it and press Run

Tab indents · ⌘/Ctrl + Enter runs · Esc leaves the editor

Line by line

  1. mod geometry { … } declares a module inline. In a real project you write mod geometry; and Cargo reads src/geometry.rs (or src/geometry/mod.rs).
  2. pub on the struct makes the type visible outside the module, and pub on each field makes that field readable and writable. Without pub x, outside code could hold a Point but not build one with Point { x, y }, which is how you keep invariants: hide the fields and offer a new that checks them.
  3. No pub: square is private to geometry and its children, like a function a JS module doesn't export.
  4. Modules nest. geometry::shapes is a child module.
  5. super is the parent module, like ../ in an import path. crate:: starts from the root of the crate, like an absolute path.
  6. use brings a path into scope, like import { Point } from "./geometry". Other crates work the same way: after cargo add rand, you write use rand::Rng;.

On disk, the same crate split across files looks like this. There is no index.ts: the mod lines in the parent file are what include the children.

text
Cargo.tomlsrc/├── main.rs          mod geometry;    ← includes src/geometry.rs├── lib.rs           (optional: the library half of the crate)├── geometry.rs      pub mod shapes;  ← includes src/geometry/shapes.rs└── geometry/    └── shapes.rstests/└── api.rs           integration tests, compiled as a separate crate

Line by line

  1. main.rs is the root of a binary crate, lib.rs of a library crate. A file that no mod line mentions is simply not compiled: no unused file can sneak into the build.
  2. A package can hold a library and a binary together. The binary then uses the library through use your_crate_name::…, like any other dependency.
  3. Files in tests/ can use only your crate's public API, like a consumer would. Unit tests live next to the code instead, as shown next.

Testing is built in

No Jest, no Vitest, no config: mark a function #[test] and cargo test runs it. Press Run on this block: a file with tests and no main runs as cargo test here too.

rustEditable: change it and press Run

Tab indents · ⌘/Ctrl + Enter runs · Esc leaves the editor

Line by line

  1. The function under test, pub so other modules (and the test module) can use it.
  2. split accepts a closure that decides which characters are separators. c: char needs its type written here because split accepts several kinds of pattern.
  3. The "turbofish" ::<Vec<_>> gives collect its target type inline, instead of annotating a variable.
  4. #[cfg(test)] compiles this module only for cargo test, so tests and test helpers never end up in your release binary.
  5. The test module is a child, so super::* imports everything from the parent, private functions included. In Rust you can unit-test private code directly.
  6. #[test] marks a test. Tests run in parallel threads by default, so they must not depend on each other.
  7. assert_eq! compares and, on failure, prints both sides. There are also assert!(condition) and assert_ne!. Try breaking the expected string and Run again to see a failure report.
  8. #[should_panic] passes only if the body panics, which is how you test that invalid input is rejected.

Smart pointers: Box, Rc and RefCell

Sometimes one owner is not enough, or a value has to live on the heap. The standard library has a few smart pointers for those cases. Each one is an ordinary struct that owns some heap memory and frees it when dropped.

rustEditable: change it and press Run

Tab indents · ⌘/Ctrl + Enter runs · Esc leaves the editor

Line by line

  1. A recursive type needs indirection: a List can't contain a whole List directly, or its size would be infinite. Box<List> is a pointer to a heap-allocated List, with a known size. Box is the simplest smart pointer: a single owner of something on the heap.
  2. A Box dereferences automatically, so rest (a &Box<List>) can be passed where a &List is expected.
  3. Building the list 1 → 2 → end. When list is dropped, each Box frees its node in turn.
  4. Rc ("reference counted") allows several owners of one value, in a single thread. It is the closest thing to how JS objects work: the value is freed when the last owner goes away.
  5. Rc::clone doesn't copy the string; it adds one to the count and returns another pointer to the same value. The Rc::clone(&x) spelling (rather than x.clone()) makes that visible to readers.
  6. Three owners now. Values inside an Rc are read-only, because shared plus mutable is exactly what the borrow rules forbid.
  7. RefCell moves the borrow check from compile time to run time: you may change the contents through a shared handle, and it panics if two mutable borrows ever overlap. Rc<RefCell<T>> is the usual recipe for shared, mutable state in one thread.
  8. borrow_mut() hands out a temporary mutable borrow, which ends at the end of the statement.
  9. borrow() is a temporary shared borrow. Use these types sparingly: most Rust code needs none of them, and reaching for Rc<RefCell<…>> everywhere is often a sign of a design that fights ownership.

Fearless concurrency: threads, channels and locks

JavaScript has one thread per event loop and offers Web Workers that share nothing by default. Rust gives you real OS threads that can share memory, and the same ownership rules stop data races at compile time. Two marker traits do the work: Send (safe to move to another thread) and Sync (safe to share between threads). The compiler simply refuses to send an Rc across threads, for example.

rustEditable: change it and press Run

Tab indents · ⌘/Ctrl + Enter runs · Esc leaves the editor

Line by line

  1. Start three threads, one per id, and keep their handles. The collect() matters: iterators are lazy, so without it no thread would start until later.
  2. thread::spawn runs a closure on a new OS thread. move makes the closure take ownership of what it uses (id), because the thread may outlive the current function, so borrowing would be unsafe.
  3. join() waits for a thread and returns its result, like awaiting a promise. It is a Result, because the thread may have panicked.
  4. To share a counter: Mutex guards the value so only one thread at a time can touch it, and Arc ("atomic reference counted") is the thread-safe Rc that lets every thread own a handle to it.
  5. Each thread gets its own Arc clone to move in. The data itself is not copied.
  6. lock() waits for the mutex and returns a guard that dereferences to the value. The lock is released when the guard is dropped, here at the end of the statement, so you can't forget to unlock. Try replacing Arc<Mutex<i32>> with a plain let mut counter = 0 captured by every thread: the compiler rejects it instead of letting a race lose updates.
  7. Always 8000. With unsynchronised threads in C this could print any number; in Rust that version doesn't compile.
  8. A channel, like Go's: tx sends and rx receives. mpsc means multiple producers, single consumer, so each thread gets a clone of tx.
  9. The receiving loop ends when every sender is gone. The worker threads drop their clones when they finish; this drops the original.
  10. rx.iter() blocks for each message until the channel closes. The threads finish in any order, so the messages are sorted to print stably.
  11. Scoped threads may borrow local data (data) without move or Arc, because thread::scope waits for all of them before it returns, so they can't outlive what they borrow.

Async Rust with Tokio

Threads are good for CPU work. For thousands of network connections you want async, which you know well from JS. The syntax is nearly the same; the model has two differences. A Rust Future is lazy: it does nothing until it is awaited or spawned, whereas a JS promise starts the moment you create it. And there is no built-in event loop: you add a runtime crate, almost always Tokio.

ts
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));async function fetchUser(id: number) {  await sleep(100 * id);  return 'user ' + id;}const [a, b] = await Promise.all([fetchUser(1), fetchUser(2)]);

Line by line

  1. Promise.all runs both at once and waits for both: about 200 ms in total, not 300.
rustEditable: change it and press Run

Tab indents · ⌘/Ctrl + Enter runs · Esc leaves the editor

Line by line

  1. An async fn returns a Future<Output = String> instead of a String, as an async function returns a Promise<string>.
  2. .await is a postfix keyword (written after the expression), so chains read left to right: client.get(url).send().await?.json().await?. Use Tokio's sleep, never std::thread::sleep, which would block the whole worker thread.
  3. This attribute macro starts the Tokio runtime and runs main on it. Tokio provides the scheduler, timers, TCP and file I/O, and channels.
  4. Calling an async fn only builds the future. Nothing happens here: no timer starts, and dropping it simply discards it. A JS promise would already be running.
  5. join! polls both futures concurrently in the current task and waits for both: Promise.all. (There is also select!, which takes whichever finishes first: Promise.race.)
  6. tokio::spawn hands a future to the runtime as an independent task, which may run on another worker thread, like firing off a promise without awaiting it yet. Tasks are cheap: a server can have hundreds of thousands.
  7. Awaiting a JoinHandle gives a Result: Err if the task panicked or was cancelled.
  8. timeout wraps any future with a deadline and returns Err(Elapsed) if it isn't done in time. The 500 ms fetch is dropped (cancelled) at 50 ms. In Rust, dropping a future is how you cancel it.
  9. The whole run takes about 550 ms. Run sequentially, the same awaits would take over 900 ms.

JSON with serde

In TypeScript you might JSON.parse and then check the shape with Zod. In Rust, serde derives the parser from your struct, so the shape is checked while parsing and you get a typed value or a precise error. It is one of the most used crates in the ecosystem.

rustEditable: change it and press Run

Tab indents · ⌘/Ctrl + Enter runs · Esc leaves the editor

Line by line

  1. Deriving Serialize and Deserialize generates the JSON code at compile time. No reflection happens at run time, which is why serde is very fast.
  2. Rust fields are snake_case and JSON is often camelCase; this attribute maps display_name to "displayName".
  3. An Option field accepts null or a missing key, as email?: string | null would in TS.
  4. #[serde(default)] fills a missing key with the type's default, an empty Vec.
  5. main itself can return a Result, so ? works in it. An Err ends the program with the error printed and a failing exit code.
  6. A raw string r#"…"# needs no escaping of the inner quotes, which is handy for JSON and regexes.
  7. Parse straight into a User. The type annotation picks what to parse into: this is the Zod schema and the TS type in one.
  8. Wrong types are caught while parsing, with the position in the input. unwrap_err() takes the error out (and would panic if parsing had succeeded).
  9. Serialising goes the other way. The struct update syntax makes a modified copy of user with one tag.
  10. serde_json::Value is for JSON whose shape you don't know, like unknown in TS. Indexing a missing key gives null instead of panicking.

unsafe, briefly

Everything so far was safe Rust: the compiler proves there are no dangling pointers or data races. Inside an unsafe block you can do five extra things, the main ones being dereferencing raw pointers and calling C functions. The compiler still checks everything else; unsafe only means "I, the programmer, guarantee these few operations are valid."

rustEditable: change it and press Run

Tab indents · ⌘/Ctrl + Enter runs · Esc leaves the editor

Line by line

  1. A raw pointer *mut i32 is a C-style pointer. Making one is safe; it is reading or writing through it that the compiler can't check.
  2. Dereferencing it must be inside unsafe. The block marks exactly where a reviewer needs to look.
  3. get_unchecked skips the bounds check that v[2] does. With an index past the end, this is undefined behaviour, which is why it is unsafe. Measure before reaching for tricks like this: the optimiser usually removes bounds checks it can prove are unneeded. Application code almost never needs unsafe.

A small project: a word-frequency report

Here is one program that uses most of what came before: a constant, a struct, a trait implementation, an iterator pipeline, a HashMap, sorting with a custom order, and Result. Change the text or the limit and run it.

rustEditable: change it and press Run

Tab indents · ⌘/Ctrl + Enter runs · Esc leaves the editor

Line by line

  1. A const string slice. In a real CLI this would come from a file (std::fs::read_to_string(path)?) or from standard input.
  2. The result type: a total and the top words with their counts, as (String, usize) tuples.
  3. Implementing Display lets the report print itself with {}, like overriding toString(). writeln! writes a line into the formatter, and ? passes on any write error.
  4. Format specs: {:>2} right-aligns in two columns and {word:<6} left-aligns in six, so the bars line up. "#".repeat(n) builds the bar, like "#".repeat(n) in JS. *n dereferences the &usize the iterator gives.
  5. Returns an owned map of word to count. The map's types are inferred from how it is used below.
  6. Splitting on any non-letter handles spaces, full stops and commas in one go.
  7. str::to_lowercase passes a method as a function, as .map(s => s.toLowerCase()) would, but shorter.
  8. An error for empty input. A plain String is fine as an error type in a small program.
  9. into_iter() consumes the map, moving its keys and values into the vector, so nothing is copied. After this, counts can't be used.
  10. Sort by count, highest first (b before a), and break ties alphabetically with then_with. cmp returns an Ordering (Less, Equal or Greater), Rust's version of a comparator's negative, zero or positive number.
  11. Handle both outcomes. eprintln! writes to standard error, like console.error.

Rust in the browser: WebAssembly

This is where Rust meets your existing stack. Rust compiles to WebAssembly, and wasm-bindgen generates the JS glue and TypeScript types, so a TS app can call Rust functions as if they were an npm module. It suits heavy work in the browser: image and video processing, parsers, compression, games, cryptography.

rust
use wasm_bindgen::prelude::*; #[wasm_bindgen]pub fn slugify(title: &str) -> String {    title.to_lowercase().split_whitespace().collect::<Vec<_>>().join("-")}

Line by line

  1. After cargo add wasm-bindgen, the prelude brings in the attribute and the JS interop types. In Cargo.toml the crate also needs crate-type = ["cdylib"] under [lib].
  2. #[wasm_bindgen] exports the function to JavaScript and generates the code that copies strings across the boundary between JS and Wasm memory.
sh
rustup target add wasm32-unknown-unknowncargo build --release --target wasm32-unknown-unknownwasm-bindgen --target web --out-dir pkg target/wasm32-unknown-unknown/release/slug.wasm

Line by line

  1. Installs the WebAssembly target once, then builds the crate as a .wasm file.
  2. The wasm-bindgen CLI (cargo install wasm-bindgen-cli) writes pkg/slug.js, pkg/slug.d.ts and the .wasm file. Bundlers such as Vite can import them directly.
ts
import init, { slugify } from './pkg/slug.js'; await init();console.log(slugify('Zero to Hero in Rust')); // "zero-to-hero-in-rust"

Line by line

  1. An ordinary ES module import. The generated .d.ts file gives slugify the type (title: string) => string, so your editor autocompletes it.
  2. init() downloads and instantiates the .wasm file once.
  3. From here on it is a normal function call. The Rust code runs at near-native speed inside the browser's sandbox.

What you can build with Rust

  • Command-line tools. clap parses arguments and the result is one fast binary. ripgrep, bat, fd, eza, starship and uv (the Python package manager) are all Rust.
  • Web backends and APIs. Axum (from the Tokio team), Actix Web and Rocket, with SQLx or Diesel for databases. Services use little memory and have very steady latency.
  • JavaScript tooling. Much of the modern JS toolchain is now Rust: SWC (the compiler behind Next.js), Turbopack (the default bundler in Next.js 16), Rspack, Rolldown and Oxc (the Vite team's bundler and parser), Biome (formatter and linter), Lightning CSS, and the Deno runtime.
  • WebAssembly for the browser, edge platforms (Cloudflare Workers runs Wasm next to JS) and plugin systems.
  • Desktop and mobile apps. Tauri gives you a web front end (React, Svelte, …) with a Rust back end, and the apps are much smaller than Electron ones.
  • Systems software: operating-system components (Linux kernel drivers, parts of Windows, Android), databases, proxies (Cloudflare's Pingora), virtual machines (AWS Firecracker).
  • Embedded and IoT. no_std Rust runs on microcontrollers with no operating system, through the embassy async framework and the HAL crates for each chip.
  • Games, graphics and data. The Bevy engine, wgpu for GPU work on every platform, and Polars, a DataFrame library much faster than pandas.

Pros and cons

Where Rust shines

  • Memory safety without a garbage collector. Google reported in November 2025 that memory-safety bugs fell below 20% of all Android vulnerabilities for the first time, and that its Rust code has about a thousand times fewer such bugs than its C and C++.
  • Speed and predictability. Performance matches C and C++, and with no GC there are no pause-time spikes, which is exactly why Discord and Figma moved services to it.
  • "If it compiles, it usually works." The type system, Option, Result and exhaustive match catch whole categories of bugs before anything runs, so refactoring a large codebase is far less frightening.
  • Fearless concurrency. Data races are compile errors.
  • Excellent tooling. Cargo, rustfmt, Clippy, rust-analyzer and built-in tests and docs (cargo doc) come with every install and work the same way in every project.
  • Compiler messages that teach. The errors explain what went wrong and usually suggest the fix.
  • Runs almost everywhere: servers, browsers via Wasm, phones, microcontrollers, as one dependency-free binary.

Where it hurts

  • A steep learning curve. Ownership, borrowing and lifetimes take weeks to feel natural. Expect a period of "fighting the borrow checker." It ends, and the habits it leaves make you a better programmer in any language.
  • Slow compile times. Monomorphisation and LLVM optimisation cost time. A large project can take minutes for a clean build. cargo check, incremental builds and splitting into crates help.
  • Slower to prototype. For a quick script or an experiment, TypeScript or Python get you there faster. Rust asks you to handle every error and edge case up front.
  • Async has rough edges. Pinning, Send bounds on futures and choosing a runtime are harder than JS's async, and error messages there are at their least friendly.
  • A younger ecosystem in some areas. Web back ends and CLIs are mature; GUI toolkits, some enterprise SDKs and ML training are thinner than in JS, Python or Java.
  • Hiring and team ramp-up. Fewer developers know Rust well than TS or Go, so a team adopting it needs time to train.

Rust, Go and TypeScript side by side

These three often live in the same company, so it helps to know what each is best at:

  • Memory. TS (V8) and Go are garbage-collected; Rust uses ownership and has no GC. Go's GC pauses are short, but under heavy load they still show up in tail latency.
  • Concurrency. TS: one event loop with async/await. Go: goroutines and channels built into the language, the simplest model of the three. Rust: threads plus async, with data races ruled out at compile time.
  • Type system. TS: structural and powerful, but erased and unsound by design (any). Go: simple, nominal, with generics since 1.18 and deliberately few features. Rust: rich (enums with data, traits, generics, lifetimes) and sound.
  • Errors. TS: exceptions. Go: if err != nil return values. Rust: Result with ?, explicit like Go but much shorter.
  • Learning curve. Go is the easiest to pick up (a weekend), TS is second if you know JS, and Rust is the hardest (weeks to months).
  • Best at. TS: UIs, full-stack web apps and sharing code between browser and server. Go: network services, CLIs and cloud infrastructure, quickly and with small teams (Docker, Kubernetes and Terraform are written in Go). Rust: performance-critical paths, systems software, tooling, embedded and Wasm.

How major companies use TypeScript/JavaScript, Go and Rust together

Big companies rarely pick a single language. A common pattern is TypeScript for the product surface, Go for the bulk of network services and infrastructure, and Rust where latency, memory or safety matter most. Some well-documented examples:

  • Discord. The desktop and web clients are JavaScript (React, with Electron on desktop). The Read States service, which tracks what every user has read, was written in Go and moved to Rust because Go's garbage collector caused latency spikes every couple of minutes. Even the first, unoptimised Rust version outperformed the tuned Go one.
  • Figma. The editor UI is TypeScript and its rendering engine is C++ compiled to WebAssembly. The multiplayer server that syncs documents started in TypeScript and was rewritten in Rust when it could no longer keep up, partly because of garbage-collection pauses. The widely used esbuild bundler, written in Go, was created by Figma co-founder Evan Wallace.
  • Microsoft. TypeScript 7 (2026) replaced the JavaScript-based compiler with a native one written in Go, about ten times faster on large projects. The team chose Go because its structure and garbage collection let them port the existing code nearly line by line. Microsoft also ships Rust in the Windows kernel, and Azure's CTO has urged new non-GC projects to use Rust instead of C/C++.
  • Cloudflare. Developers write Workers in JavaScript and TypeScript (or Rust compiled to Wasm). Its HTTP proxy, Pingora, was written in Rust to replace NGINX and handles over a trillion requests a day; it is now open source. Many internal services are written in Go.
  • Vercel. Next.js is TypeScript, but the compiler underneath is Rust: SWC transforms the code, and Turbopack, also Rust, became the default bundler in Next.js 16.
  • Amazon Web Services. Firecracker, the micro-VM that runs every AWS Lambda function and Fargate task, is written in Rust. AWS CDK, its infrastructure-as-code toolkit, is written in TypeScript, and AWS maintains official SDKs for Go, JavaScript and Rust.
  • Google. It created Go and uses it across its cloud, and Angular (TypeScript) is its front-end framework. It writes new low-level Android code in Rust, with the security results above.
  • Dropbox. Its desktop sync engine, Nucleus, was rewritten in Rust, using the type system to "design away invalid states" so the engine is easier to test.
  • The Linux kernel. At the December 2025 Maintainers Summit, Rust was declared no longer experimental: it is now a permanent part of the kernel alongside C.

The lesson for a JS/TS developer: you don't have to choose. Keep TypeScript where it shines, and reach for Go when you need a simple, fast service, or for Rust when you hit a real performance, memory or safety wall. Since so much of your toolchain is now written in Go and Rust, learning them also lets you read and contribute to the tools you use every day.

Your roadmap from zero to hero

  1. Week 1: the basics. Install with rustup, set up rust-analyzer in your editor, and run every example on this page, changing each one until it breaks. Read chapters 1–6 of The Rust Programming Language ("the book", free at doc.rust-lang.org/book).
  2. Week 2: ownership for real. Do the Rustlings exercises. When the borrow checker complains, read the whole error message, and the suggested fix, before changing anything.
  3. Week 3: a CLI tool. Rewrite a small Node script you use in Rust with clap, serde and anyhow. Make Clippy pass with no warnings.
  4. Week 4: traits, generics and testing. Finish the book up to chapter 11 and write unit tests for your CLI.
  5. Month 2: async and the web. Work through the Tokio tutorial, then build a small JSON API with Axum and SQLx, and call it from a TypeScript front end.
  6. Month 3: go deeper. Concurrency (book chapter 16), smart pointers and lifetimes in depth, and Rust for Rustaceans by Jon Gjengset. Compile something to WebAssembly and use it from a React app.
  7. Keep going. Read the release notes every six weeks, follow This Week in Rust, and read the source of tools you already use, such as SWC, Biome or ripgrep. Fix a small issue in one of them.

Rust makes you think about ownership up front, the thing JavaScript lets you ignore until it bites. The compiler is strict, but it is on your side: once it lets you through, a whole category of late-night bugs is gone.

— Field note

JavaScript ဒါမှမဟုတ် TypeScript နဲ့ အသက်မွေးနေသူဆိုရင် Rust က တခြားဂြိုဟ်က ဘာသာစကားလို ထင်ရနိုင်ပါတယ် — lifetime တွေ၊ borrow checker တွေ၊ &mut တွေ၊ စာကြောင်းနှစ်ဆယ်လောက် ရှည်တဲ့ compiler error တွေ။ ဒါပေမဲ့ အထဲကို ဝင်ကြည့်ရင် အများကြီးက ရင်းနှီးနေပါလိမ့်မယ်။ Closure တွေ၊ map နဲ့ filter ပါတဲ့ iterator တွေ၊ async/await၊ npm လိုပဲ အလုပ်လုပ်တဲ့ package manager၊ ပြီးတော့ TypeScript လုပ်ပေးတာတွေကို လုပ်ပေးရုံမက program က free လုပ်ပြီးသား memory ကို မထိနိုင်ကြောင်း၊ shared data ပေါ်မှာ race မဖြစ်နိုင်ကြောင်းပါ သက်သေပြပေးတဲ့ type system တစ်ခု ပါပါတယ်။

ဒီ guide က သုညကနေ စပြီး တကယ့် Rust code ကို ဖတ်နိုင်၊ program အသေးလေးတစ်ခု ကိုယ်တိုင် ရေးနိုင်တဲ့ အဆင့်အထိ ခေါ်သွားပါမယ်။ အပိုင်းတိုင်းမှာ Rust idea တစ်ခုကို သင်သိပြီးသား JS/TS idea နဲ့ ဘေးချင်းယှဉ်ပြထားပါတယ်။ ဥပမာတိုင်းအောက်မှာ တစ်ကြောင်းချင်း မှတ်စု တွေ ပါပါတယ် — highlight လုပ်ထားတဲ့ စာကြောင်းကို နှိပ်ပြီး (ဒါမှမဟုတ် hover လုပ်ပြီး) ဘာလုပ်လဲ ဖတ်ကြည့်ပါ။ Rust ဥပမာတွေက live ပါ။ ပြင်ပြီး Run ကို နှိပ်ရင် (ဒါမှမဟုတ် ⌘/Ctrl + Enter) တရားဝင် Rust Playground ပေါ်မှာ compile လုပ်ပြီး run ပေးပါတယ်၊ ဒီစာမျက်နှာပေါ်မှာတင်ပါပဲ။

JS/TS developer တစ်ယောက်အတွက် ဘာကြောင့် Rust လဲ

Rust ကို ၂၀၀၆ ခုနှစ်မှာ Mozilla က Graydon Hoare ရဲ့ ကိုယ်ပိုင် project လေးအဖြစ် စခဲ့ပြီး ၂၀၀၉ ကစပြီး Mozilla က ထောက်ပံ့ခဲ့ကာ ၂၀၁၅ မေလမှာ 1.0 ထွက်ခဲ့ပါတယ်။ ၂၀၂၁ ကစပြီး AWS၊ Google၊ Microsoft နဲ့ Huawei တို့ အဖွဲ့ဝင်ဖြစ်တဲ့ လွတ်လပ်တဲ့ Rust Foundation က စောင့်ရှောက်နေပါတယ်။ Stack Overflow survey မှာ developer တွေက ၂၀၁၆ ကစပြီး နှစ်တိုင်း အချစ်ဆုံး (most admired) ဘာသာစကားအဖြစ် ရွေးခဲ့ကြပါတယ်။

အရင်က တစ်ခုရရင် တစ်ခုစွန့်ရတဲ့ အရာနှစ်ခုကို တစ်ပြိုင်နက် ပေးဖို့ ရည်ရွယ်ပါတယ် — C နဲ့ C++ ရဲ့ အမြန်နှုန်းနဲ့ ထိန်းချုပ်နိုင်မှု၊ ပြီးတော့ JavaScript လို garbage-collected ဘာသာစကားရဲ့ memory safety၊ garbage collector မပါဘဲနဲ့ပေါ့။ သင်သိပြီးသားတွေနဲ့ ယှဉ်ရင် ဘာတွေ ပြောင်းသွားလဲ ကြည့်ရအောင်—

  • Garbage collector မရှိ၊ runtime မရှိ။ V8 က မသုံးတော့တဲ့ object တွေကို ရှာပြီး free လုပ်ဖို့ ခဏခဏ ရပ်ပါတယ်။ Rust ကတော့ value တစ်ခုချင်းကို compiler က ကြိုဆုံးဖြတ်ထားတဲ့ နေရာမှာ free လုပ်လို့ ရပ်တာ မရှိဘဲ memory သုံးစွဲမှုက တည်ငြိမ်ပြီး ကြိုခန့်မှန်းလို့ ရပါတယ်။
  • Type တွေကို စစ်ပြီးမှ compile လုပ်ရင်း ဖယ်ပစ်တယ်။ TypeScript လိုပဲ Rust က program မ run ခင် type စစ်ပါတယ်။ TypeScript နဲ့ မတူတာက any မရှိ၊ as unknown as မရှိပါဘူး၊ ပြီးတော့ ထွက်လာမယ့် machine code ကို type တွေက ဆုံးဖြတ်ပါတယ်။
  • "အရာအားလုံးက shared reference" ဆိုတာကို ownership က အစားထိုးတယ်။ JS မှာ variable ဘယ်နှစ်ခုမဆို object တစ်ခုတည်းကို ညွှန်ပြီး ပြင်နိုင်ပါတယ်။ Rust မှာတော့ value တစ်ခုစီမှာ owner တစ်ယောက်ပဲ ရှိပြီး ဘယ်သူက ဘယ်အချိန် ဖတ်ခွင့်/ရေးခွင့် ရှိလဲဆိုတာကို borrowing rule တွေက ဆုံးဖြတ်ပါတယ်။ Bug အမျိုးအစား တစ်ခုလုံးကို ဖယ်ရှားပေးတာ အဲဒီ rule တွေပါပဲ။
  • Error တွေက value တွေပါ။ Catch ဖို့ မေ့သွားနိုင်တဲ့ exception မရှိပါဘူး။ Fail ဖြစ်နိုင်တဲ့ function က Result ကို return ပြန်ပြီး compiler က သင့်ကို ကိုင်တွယ်ခိုင်းပါတယ်။
  • null လည်းမရှိ၊ undefined လည်းမရှိ။ မရှိနိုင်တဲ့ value က Option ဖြစ်ပြီး "မရှိ" ဆိုတဲ့ case ကို ကိုင်တွယ်ပြီးမှ သုံးလို့ ရပါတယ်။
  • Binary တစ်ခုတည်း ထွက်လာတယ်။ cargo build --release က native executable တစ်ခုတည်း ထုတ်ပေးလို့ run မယ့်နေရာမှာ Node လည်း မလို၊ node_modules လည်း မလို၊ interpreter လည်း မလိုပါဘူး။

Rust ဘယ်လို အလုပ်လုပ်လဲ — architecture

Node က သင့် JavaScript ကို run နေချိန်မှာ ဖတ်ပြီး V8 က ချက်ချင်း compile လုပ်ကာ မကြာခဏ ခေါ်တဲ့ function တွေကို type တွေ သိလာသလို ပြန်ပြီး optimise လုပ်ပါတယ်။ Rust ကတော့ အဲဒီအလုပ်အားလုံးကို program မဖြစ်လာခင်မှာပဲ rustc compiler ထဲမှာ လုပ်ပါတယ်။ အဆင့်တွေကို သိထားရင် compiler ရဲ့ error message တွေကို ဖတ်ရတာ အများကြီး ပိုလွယ်ပါတယ်—

  1. Parsing နဲ့ macro expansion။ Source text က syntax tree ဖြစ်လာပါတယ်။ println!၊ vec! လို macro တွေ (! က macro ဖြစ်ကြောင်း ပြတာပါ) ကို ဒီမှာ ရိုးရိုး code အဖြစ် ချဲ့ပြီး #[derive(...)] attribute တွေက သင့် type တွေအတွက် code ထုတ်ပေးပါတယ်။
  2. Name resolution နဲ့ HIR။ နာမည်တွေကို သူတို့ရဲ့ definition တွေနဲ့ ချိတ်ပြီး tree ကို High-level IR ဆိုတဲ့ ရိုးရှင်းအောင် ပြောင်းထားတဲ့ ပုံစံ (desugared form) ဆီ ချပါတယ် (ဥပမာ for loop က match ပါတဲ့ loop ဖြစ်သွားပါတယ်)။
  3. Type checking နဲ့ trait resolution။ Type တွေကို infer လုပ်ပြီး စစ်ကာ method call တိုင်းကို တိကျတဲ့ implementation တစ်ခုနဲ့ ချိတ်ပါတယ်။ TypeScript ဆိုရင် ဒီမှာ ရပ်သွားမှာပါ။
  4. MIR နဲ့ borrow checker။ Program ကို Mid-level IR ဆိုတဲ့ ရိုးရှင်းတဲ့ statement တွေနဲ့ ဖွဲ့ထားတဲ့ control-flow graph ဆီ ထပ်ချပါတယ်။ Borrow checker က ဒီ graph ကို လျှောက်ကြည့်ပြီး reference တိုင်း valid ဖြစ်ကြောင်း၊ တခြားတစ်ယောက် ဖတ်နေတုန်း ဘယ် value ကိုမှ မရေးကြောင်း သက်သေပြပါတယ်။ Ownership error တွေ ဒီကလာတာပါ။
  5. Monomorphisation။ Generic code (fn largest<T>) ကို သုံးတဲ့ တိကျတဲ့ type တစ်ခုစီအတွက် တစ်ခါစီ ကူးထုတ်လို့ largest::<i32> နဲ့ largest::<f64> က runtime type စစ်ဆေးမှု မပါတဲ့ သီးသန့် function နှစ်ခု ဖြစ်သွားပါတယ်။ Rust ရဲ့ generic တွေက run နေချိန်မှာ ဘာမှ မကုန်ကျတာ ဒါကြောင့်ပါ။
  6. LLVM နဲ့ linker။ MIR ကို Clang နဲ့ Swift သုံးတဲ့ optimiser back end ဖြစ်တဲ့ LLVM IR ဆီ ဘာသာပြန်ပြီး သင့် target (x86-64၊ ARM၊ WebAssembly…) အတွက် machine code ထုတ်ပါတယ်။ Linker က သင့် crate တွေကို binary တစ်ခုတည်းအဖြစ် ပေါင်းပါတယ်။

ဒီဇိုင်းရဲ့ တခြားအစိတ်အပိုင်း တချို့ကိုလည်း ကြိုသိထားသင့်ပါတယ်—

  • Zero-cost abstraction တွေ။ Iterator၊ closure၊ Option နဲ့ generic တွေက loop နဲ့ pointer တွေနဲ့ ကိုယ်တိုင် ရေးမယ့် machine code အတိုင်းပဲ compile ဖြစ်ပါတယ်။ အဆင့်မြင့်ပုံစံနဲ့ ရေးလို့ run နေချိန်မှာ ပိုမကုန်ပါဘူး။
  • Crate နဲ့ Cargo။ Crate ဆိုတာ compile လုပ်တဲ့ ယူနစ်တစ်ခု၊ library ဒါမှမဟုတ် binary တစ်ခုပါ (npm package တစ်ခုလို့ မြင်ကြည့်ပါ)။ Cargo က crate တွေကို build၊ test၊ document နဲ့ publish လုပ်ပေးပြီး crates.io က registry ပါ။
  • std၊ alloc နဲ့ core။ Standard library က အလွှာလိုက် လာပါတယ်။ core က operating system မပါတဲ့ microcontroller ပေါ်မှာတောင် ဘယ်နေရာမှာမဆို အလုပ်လုပ်ပါတယ်။ alloc က Vec၊ String လို heap type တွေ ထပ်ထည့်ပါတယ်။ std က file၊ thread နဲ့ networking ထပ်ထည့်ပါတယ်။ Embedded နဲ့ kernel code က အောက်အလွှာတွေကိုပဲ သုံးပါတယ်။
  • Edition တွေ။ နှစ်အနည်းငယ်တစ်ခါ (2015၊ 2018၊ 2021၊ 2024) edition တစ်ခုက syntax ရဲ့ breaking change အသေးလေးတွေကို စုထုတ်ပါတယ်။ Crate တစ်ခုစီက Cargo.toml ထဲမှာ ကိုယ့် edition ကို ရွေးပြီး edition မတူတဲ့ crate တွေကလည်း လွတ်လပ်စွာ ချိတ်ဆက်သုံးလို့ ရလို့ Python 2 နဲ့ 3 လို ecosystem နှစ်ခြမ်း ကွဲတာ ဘယ်တော့မှ မဖြစ်ပါဘူး။
  • Built-in runtime မပါတဲ့ async။ ဘာသာစကားက async/await နဲ့ Future trait ကို ပေးပေမဲ့ event loop မပေးပါဘူး။ Runtime library ကို ကိုယ်တိုင် ရွေးရပြီး server တွေအတွက် စံက Tokio ပါ။ JS promise တွေက ဖန်တီးလိုက်တာနဲ့ run စပေမဲ့ Rust future တွေက poll မလုပ်မချင်း ဘာမှ မလုပ်ပါဘူး။
  • ကန့်သတ်ထားတဲ့ နယ်မြေအဖြစ် unsafe။ Compiler မစစ်နိုင်တဲ့ လုပ်ဆောင်ချက် အနည်းငယ် (raw pointer တွေ၊ C ကို ခေါ်တာ) ကို unsafe { } ထဲမှာ ရေးရပါတယ်။ Safe Rust ကို standard library ထဲက စစ်ဆေးပြီးသား unsafe core အသေးလေးတွေပေါ်မှာ တည်ဆောက်ထားတာပါ။

Rust ကို install လုပ်ခြင်း

တရားဝင် toolchain manager ဖြစ်တဲ့ rustup (Rust ရဲ့ nvm) နဲ့ install လုပ်ပါ — macOS ဒါမှမဟုတ် Linux မှာ rustup.rs က one-liner ကို run ပြီး Windows မှာ အဲဒီ site ကပဲ rustup-init.exe ကို download လုပ်ပါ။ Compiler rustc၊ package manager cargo၊ formatter နဲ့ linter ကို install လုပ်ပေးပါတယ်။ ပြီးရင် နေ့တိုင်း သုံးမယ့် command တွေ—

sh
rustup update             # update to the newest stable Rustcargo new hello           # a new project with Cargo.toml and src/main.rscd hellocargo run                 # build in debug mode and runcargo check               # type-check and borrow-check only, much fastercargo test                # run every #[test] functioncargo clippy              # the linter, like ESLintcargo fmt                 # the formatter, like Prettiercargo add serde --features derive   # like npm install serdecargo build --release     # an optimised binary in target/release/

တစ်ကြောင်းချင်း ရှင်းလင်းချက်

  1. Rust က ခြောက်ပတ် တစ်ခါ stable version အသစ် ထုတ်ပါတယ်။ rustup update က အဲဒီဆီ ရွှေ့ပေးပါတယ်။ Edition တစ်ခုအတွင်း stability ကို Rust က ကတိပေးထားလို့ code ဟောင်းတွေ ဆက် compile ဖြစ်ပါတယ်။
  2. Cargo.toml (သင့် package.json)၊ src/main.rs (entry point) နဲ့ git repository ပါတဲ့ folder တစ်ခု ဖန်တီးပါတယ်။ Library အတွက်ဆိုရင် cargo new --lib name ကို သုံးပါ။
  3. Compile လုပ်ပြီး run ပါတယ်။ ပထမ build က dependency အားလုံးကို compile လုပ်လို့ နှေးပြီး နောက်ပိုင်း build တွေက incremental ပါ။ Build output က dist/ နေရာမှာ ရှိတဲ့ target/ ထဲ ရောက်ပြီး git ထဲ မထည့်ပါဘူး။
  4. Code generation ကလွဲပြီး အားလုံး run ပါတယ်။ ရေးနေတုန်း သုံးပါ၊ tsc --noEmit နဲ့ အနီးစပ်ဆုံးပါ။ rust-analyzer ပါတဲ့ editor တွေက save လုပ်တိုင်း run ပေးပါတယ်။
  5. သင့် code ထဲနဲ့ tests/ ထဲက test တွေကို ရှာပြီး run ပါတယ်။ Install လုပ်စရာ Jest လည်း မရှိ၊ Vitest လည်း မရှိပါဘူး — testing က ဘာသာစကားနဲ့ Cargo ထဲမှာ ပါပြီးသားပါ။
  6. Clippy မှာ idiomatic မဖြစ်တဲ့ ဒါမှမဟုတ် နှေးတဲ့ code ကို ထောက်ပြပြီး များသောအားဖြင့် ပြင်နည်းပါ အကြံပြုတဲ့ lint ရာနဲ့ချီ ပါပါတယ်။ Reviewer တစ်ယောက်လို သဘောထားပါ။
  7. rustfmt က code ကို တရားဝင် style တစ်မျိုးတည်းနဲ့ format လုပ်ပါတယ်။ Rust project အားလုံးနီးပါး သုံးလို့ style အငြင်းပွားစရာ မရှိပါဘူး။
  8. Cargo.toml ထဲကို dependency တစ်ခု ထည့်ပါတယ်။ Feature က crate ရဲ့ optional အပိုင်းတွေကို ဖွင့်ပေးတာပါ — serde ရဲ့ derive feature က #[derive(Serialize, Deserialize)] ကို ပေးပါတယ်။
  9. Optimisation အပြည့်နဲ့ build လုပ်ပါတယ်။ Debug build တွေက release build ထက် ဆယ်ဆ ဒါမှမဟုတ် ပိုနှေးနိုင်လို့ benchmark ကို release build နဲ့ပဲ လုပ်ပါ။

Project ရဲ့ manifest က TOML file ပါ။ သင်သိပြီးသားနဲ့ ယှဉ်ကြည့်ရအောင်—

toml
[package]name = "hello"version = "0.1.0"edition = "2024" [dependencies]serde = { version = "1.0", features = ["derive"] }tokio = { version = "1", features = ["full"] } [dev-dependencies]pretty_assertions = "1"

တစ်ကြောင်းချင်း ရှင်းလင်းချက်

  1. [package] table က သင့် package.json ရဲ့ အပေါ်ပိုင်းပါ — name နဲ့ version (Cargo က npm လိုပဲ semver သုံးပါတယ်)။
  2. ဒီ crate ကို ရေးထားတဲ့ edition ပါ။ Project အသစ်တွေက 2024 ရပါတယ်။ Syntax rule အနည်းငယ်ကိုပဲ ပြောင်းပြီး ဘယ် crate တွေ သုံးလို့ရလဲဆိုတာကို ဘယ်တော့မှ မပြောင်းပါဘူး။
  3. Runtime dependency တွေ၊ package.json ထဲက dependencies လိုပါပဲ။ တိကျတဲ့ version တွေကို package-lock.json နေရာမှာ ရှိတဲ့ Cargo.lock ထဲမှာ pin လုပ်ထားပါတယ် — application တွေအတွက် commit လုပ်ပါ။
  4. "1.0" ဆိုတာ "1.0 ဒါမှမဟုတ် အသစ်၊ 2.0 အောက်" လို့ အဓိပ္ပာယ်ရပြီး npm ရဲ့ ^1.0 နဲ့ အတူတူပါ။ Feature တွေကို dependency တစ်ခုချင်း ရွေးလို့ သုံးတာကိုပဲ compile လုပ်ပါတယ်။
  5. Test၊ example နဲ့ benchmark တွေမှာပဲ သုံးတဲ့ dependency တွေ — devDependencies ပေါ့။

Hello, Rust

Program တိုင်း fn main ကနေ စပါတယ်။ Run ကို နှိပ်ပြီး compile လုပ်ကာ run ကြည့်ပါ၊ ပြီးရင် name ကို ပြောင်းကြည့်ပါ။

rustပြင်ပြီး Run ကို နှိပ်ကြည့်ပါ

Tab က indent · ⌘/Ctrl + Enter က run · Esc က editor ထဲက ထွက်

တစ်ကြောင်းချင်း ရှင်းလင်းချက်

  1. fn က function ကြေညာတာဖြစ်ပြီး binary တစ်ခု စတာ main ကနေပါ။ JS module လို top-level code မရှိပါဘူး — အားလုံး main ကနေ run ပါတယ်။ Block တွေက brace သုံးပြီး indentation က ထုံးစံအရ space လေးခုပါ။
  2. let က variable ဖန်တီးပါတယ်။ သူ့ type ဖြစ်တဲ့ &str (borrow လုပ်ထားတဲ့ string) ကို TypeScript က const name = "Rust" ကို infer လုပ်သလိုပဲ infer လုပ်ပါတယ်။ Statement တွေ ; နဲ့ ဆုံးပါတယ်။
  3. Integer literal တစ်ခုပါ။ တခြား အချက်အလက် မရှိရင် Rust က 32-bit signed integer ဖြစ်တဲ့ i32 ကို ရွေးပါတယ်။ number type တစ်ခုတည်း မရှိပါဘူး — integer နဲ့ float က သီးခြားစီပါ။
  4. println! က function မဟုတ်ဘဲ macro (!) ပါ — format string ကို compile time မှာ စစ်ပါတယ်။ {name} က template literal ထဲက ${name} လိုပဲ variable ကို ထည့်ပေးပါတယ်။ နာမည် စာလုံးပေါင်း မှားရင် undefined မဟုတ်ဘဲ compile error ဖြစ်ပါတယ်။
  5. ဗလာ {} placeholder တွေကို အပို argument တွေနဲ့ အစဉ်လိုက် ဖြည့်ပါတယ်။ {} က type ရဲ့ user-facing Display format ကို သုံးပါတယ်။
  6. Method တွေကို JS လိုပဲ dot နဲ့ ခေါ်ပါတယ်။ to_uppercase က ဒီ variable က ပိုင်တဲ့ heap ပေါ်က ကြီးထွားနိုင်တဲ့ string ဖြစ်တဲ့ String အသစ် တစ်ခု return ပြန်ပါတယ်။
  7. Placeholder တွေကို ရောသုံးလို့ ရပါတယ်။ len() က အရှည်ကို byte နဲ့ ပြန်ပေးပါတယ် (စာသားက ASCII ဖြစ်လို့ ဒီမှာ စာလုံးအရေအတွက်နဲ့ တူပါတယ်)။

Variable၊ mutability နဲ့ shadowing

TypeScript မှာ const က variable ကို ပြန် assign မလုပ်ခိုင်းပေမဲ့ သူညွှန်ထားတဲ့ object ကိုတော့ ပြင်လို့ ရနေဆဲပါ။ Rust က ပိုတင်းကျပ်ပြီး ပိုရိုးရှင်းပါတယ် — mut လို့ မရေးမချင်း binding တစ်ခုက အောက်ဆုံးအထိ immutable ပါ။

ts
const score = 10;      // can't be reassignedlet lives = 3;         // can belives -= 1;const user = { name: 'Ada' };user.name = 'Grace';   // allowed: const fixes the binding, not the object

တစ်ကြောင်းချင်း ရှင်းလင်းချက်

  1. JS မှာ const နဲ့ let ကြား ရွေးရပြီး style guide အများစုက const ကို ပိုကြိုက်ပါတယ်။
  2. JS ရဲ့ အံ့သြစရာက ဒါပါ — const က object ကို immutable မဖြစ်စေပါဘူး။ အဲဒီအတွက် Object.freeze ဒါမှမဟုတ် Readonly<T> လိုပြီး အဲဒါတွေကတောင် shallow ပဲ ဖြစ်ပါတယ်။
rustပြင်ပြီး Run ကို နှိပ်ကြည့်ပါ

Tab က indent · ⌘/Ctrl + Enter က run · Esc က editor ထဲက ထွက်

တစ်ကြောင်းချင်း ရှင်းလင်းချက်

  1. ရိုးရိုး let က const လိုပဲ immutable ပေမဲ့ အောက်ဆုံးအထိ ပါ — score က struct တစ်ခုဆိုရင် သူ့ field တွေထဲက ဘယ်တစ်ခုမှလည်း ပြောင်းလို့ မရပါဘူး။
  2. // ကို ဖျက်ပြီး Run ကို နှိပ်ကာ compiler ငြင်းတာ ကြည့်ပါ။ Rust ရဲ့ error message တွေက အထောက်အကူဖြစ်တာ နာမည်ကြီးပါတယ် — ဒီတစ်ခုက စာကြောင်းနှစ်ကြောင်းလုံးကို ထောက်ပြပြီး mut ထည့်ဖို့ အကြံပြုပါတယ်။
  3. let mut က binding ကို JS ရဲ့ let လို mutable ဖြစ်စေပါတယ်။ Mutability က ထင်ရှားရှင်းလင်းလို့ ဖတ်သူက ဘယ် value တွေ ပြောင်းလဲလဲ တစ်ချက်ကြည့်ရုံနဲ့ သိနိုင်ပါတယ်။
  4. Shadowing — ဒုတိယ let input က နာမည်တူ variable အသစ်တစ်ခု ဖန်တီးတာပါ၊ ဒီမှာ type မတူပါဘူး။ "Parse လုပ်ပြီး စာသားကို မေ့ပစ်" ဆိုတဲ့ အဆင့်တွေမှာ အသုံးများပါတယ်။ parse() က Result ပြန်ပေးပြီး unwrap() က value ကို ထုတ်ယူပါတယ် (parse မအောင်မြင်ရင် crash ဖြစ်ပါတယ်၊ နောက်မှ ပိုပြောပါမယ်)။
  5. const က compile-time constant ပါ။ Type ကို ထင်ရှားစွာ ရေးရပြီး SCREAMING_CASE နဲ့ ရေးကာ သုံးတဲ့နေရာတိုင်းမှာ inline လုပ်ပါတယ်။ JS ရဲ့ const ထက် config file ထဲ ထည့်မယ့် value နဲ့ ပိုနီးစပ်ပါတယ်။

Type တွေ — number၊ text၊ tuple နဲ့ array

JavaScript မှာ number (64-bit float) တစ်ခုနဲ့ bigint ပဲ ရှိပါတယ်။ Rust မှာတော့ size တစ်ခုစီအတွက် type တစ်ခုစီ ရှိလို့ value တစ်ခု memory ဘယ်လောက် ယူမလဲ၊ အနုတ်ဖြစ်နိုင်လား ဆိုတာ တိတိကျကျ ရွေးရပါတယ်။

rustပြင်ပြီး Run ကို နှိပ်ကြည့်ပါ

Tab က indent · ⌘/Ctrl + Enter က run · Esc က editor ထဲက ထွက်

တစ်ကြောင်းချင်း ရှင်းလင်းချက်

  1. Signed integer တွေက i8၊ i16၊ i32၊ i64 နဲ့ i128 ပါ — isize က pointer တစ်ခုရဲ့ size ပါ။ i32 က default ပါ။
  2. Unsigned integer တွေ (u8 … u128၊ usize) က အနုတ် မဖြစ်နိုင်ပါဘူး။ u8 တစ်ခုက 0 ကနေ 255 အထိ၊ byte တစ်ခု သိမ်းပါတယ်။ usize က length တွေနဲ့ index တွေရဲ့ type ပါ။
  3. Float တွေက f32 နဲ့ f64 ပါ — f64 က JS number နဲ့ အတူတူပါ။ _f64 လို suffix က literal ရဲ့ type ကို သတ်မှတ်ပြီး _ ကို ဂဏန်းတွေကြား ဘယ်နေရာမှာမဆို ခွဲဖို့ သုံးလို့ရပါတယ် (1_000_000)။
  4. char တစ်ခုက Unicode scalar value တစ်ခု၊ လေး byte ဖြစ်ပြီး single quote နဲ့ ရေးလို့ မြန်မာ အက္ခရာ တစ်လုံးက char တစ်ခုပါ။ String တွေက double quote သုံးပါတယ်။
  5. 255 + 1 က u8 ထဲ မဆံ့ပါဘူး။ Debug build မှာ ရိုးရိုး + က overflow ဖြစ်ရင် တိတ်တဆိတ် ပတ်မလည်ဘဲ panic ဖြစ် (program ရပ်) ပါတယ်။ Overflow ဖြစ်နိုင်ရင် ကိုယ်လိုချင်တာကို ပြောပါ — checked_add က overflow ဖြစ်ရင် None ပြန်ပေးပြီး wrapping_add က 0 ဆီ ပတ်လည်ကာ saturating_add က အမြင့်ဆုံးမှာ ရပ်ပါတယ်။
  6. Integer division က ဒသမကို ဖြတ်ပစ်လို့ 7 / 2 က 3 ဖြစ်ပြီး float နဲ့ဆိုရင် 3.5 ပါ။ JS မှာ နှစ်ခုလုံး 3.5 ရပါတယ်။
  7. Rust က number type တွေကို ကိုယ့်ဘာသာ ဘယ်တော့မှ မပြောင်းပေးပါဘူး၊ i32 ကနေ f64 တောင် မပြောင်းပါဘူး။ as က ထင်ရှားစွာ ပြောင်းပါတယ်။ အဲဒါမပါရင် a * c က compile error ပါ — type တွေ ရောတာက အမြဲတမ်း သင် ချရေးရတဲ့ ဆုံးဖြတ်ချက်ပါ။
  8. Tuple က type မတူတဲ့ value တွေကို TS tuple [number, number] လို စုပေးပါတယ်။ let (x, y) နဲ့ destructure လုပ်ပါ၊ ဒါမှမဟုတ် .0၊ .1 နဲ့ နေရာအလိုက် field ကို ဖတ်ပါ။
  9. Array တစ်ခုမှာ သူ့ type ရဲ့ အစိတ်အပိုင်းဖြစ်တဲ့ ([&str; 3]) ပုံသေ length ရှိပြီး stack ပေါ်မှာ နေပါတယ်။ အဆုံးကျော်ပြီး index လုပ်ရင် undefined ပြန်မပေးဘဲ panic ဖြစ်ပါတယ်။ ကြီးထွားတဲ့ list အတွက် နောက်မှာ ပြမယ့် Vec ကို သုံးပါ။

Function တွေ၊ ပြီးတော့ အရာအားလုံးက expression

နေ့စဉ် ရေးပုံမှာ အကြီးဆုံး ပြောင်းလဲမှု — Rust မှာ if၊ match နဲ့ block တစ်ခုလုံးအပါအဝင် အရာအားလုံးနီးပါးက value တစ်ခု ထုတ်ပေးတဲ့ expression ပါ။ Semicolon မပါဘဲ ရေးထားတဲ့ block ရဲ့ နောက်ဆုံး expression က အဲဒီ block ရဲ့ value ပါ။

rustပြင်ပြီး Run ကို နှိပ်ကြည့်ပါ

Tab က indent · ⌘/Ctrl + Enter က run · Esc က editor ထဲက ထွက်

တစ်ကြောင်းချင်း ရှင်းလင်းချက်

  1. Parameter type တွေက strict TypeScript လိုပဲ မဖြစ်မနေ ရေးရပြီး နာမည်နောက်မှာ လာပါတယ်။ -> i32 က return type ပါ။ TS နဲ့ မတူတာက Rust က function ရဲ့ signature ကို ဘယ်တော့မှ infer မလုပ်ပါဘူး — signature တွေက စာချုပ်ဖြစ်ပြီး inference က body ထဲမှာပဲ အလုပ်လုပ်ပါတယ်။
  2. return လည်း မပါ၊ semicolon လည်း မပါ — နောက်ဆုံး expression ကို return ပြန်ပါတယ်။ ; ထည့်လိုက်ရင် value က () (ဗလာ "unit" type၊ Rust ရဲ့ void) ဖြစ်တဲ့ statement ဖြစ်သွားပြီး function က ဘာမှ return မပြန်ဘူးလို့ compiler က ညည်းပါလိမ့်မယ်။ return ကတော့ စောစော ထွက်ဖို့ ရှိပါတယ်။
  3. &'static str က program တစ်ခုလုံး အသက်ရှင်နေတဲ့ string slice ပါ — string literal တွေကို binary ထဲမှာပဲ သိမ်းထားလို့ အမြဲ valid ဖြစ်ပါတယ်။
  4. if က expression ဖြစ်လို့ JS ရဲ့ ternary operator လို အလုပ်လုပ်ပါတယ်။ Branch တိုင်းမှာ type တူရပြီး condition က တကယ့် bool ဖြစ်ရပါမယ် — truthiness မရှိလို့ if 1 { } က compile မဖြစ်ပါဘူး။
  5. Brace ထဲက block ကလည်း expression ပါ။ ယာယီ w နဲ့ h က အထဲမှာပဲ ရှိပြီး သူ့ value က w * h ပါ။ Helper variable တွေ အပြင်မပေါက်ဘဲ အဆင့်အနည်းငယ်နဲ့ value တစ်ခု တွက်ဖို့ သပ်ရပ်တဲ့ နည်းပါ။

Control flow — loop တွေနဲ့ match

rustပြင်ပြီး Run ကို နှိပ်ကြည့်ပါ

Tab က indent · ⌘/Ctrl + Enter က run · Esc က editor ထဲက ထွက်

တစ်ကြောင်းချင်း ရှင်းလင်းချက်

  1. loop က break မတွေ့မချင်း အဆုံးမရှိ ထပ်ခါထပ်ခါ လုပ်ပါတယ်။ while (true) ကို အစားထိုးပြီး expression ဖြစ်လို့ value ထုတ်ပေးနိုင်ပါတယ်။
  2. break count က loop ထဲက ထွက်ရုံသာမက count ကို loop ရဲ့ value အဖြစ် ထုတ်ပေးလို့ found က 8 ဖြစ်လာပါတယ်။ JS မှာဆိုရင် loop အပြင်မှာ ကြေညာထားတဲ့ variable တစ်ခု လိုပါလိမ့်မယ်။
  3. while က JS လိုပဲ အလုပ်လုပ်ပြီး condition ပတ်လည်မှာ parentheses မလိုပါဘူး။ print! က newline မပါတဲ့ println! ပါ။
  4. for က iterate လုပ်လို့ရတဲ့ ဘာကိုမဆို loop ပတ်ပါတယ်။ 1..=3 က အဆုံးပါဝင်တဲ့ range (1၊ 2၊ 3) ဖြစ်ပြီး 1..3 က အဆုံးကို မပါပါဘူး (1၊ 2)။ C-style for (let i = 0; i < n; i++) မရှိပေမဲ့ သိပ်မလွမ်းရပါဘူး။
  5. .iter().enumerate() က (index, item) အတွဲတွေ ထုတ်ပေးပြီး loop head မှာ destructure လုပ်ပါတယ်၊ for (const [i, lang] of langs.entries()) လိုပေါ့။
  6. match က superpower ပါတဲ့ switch ပါ — expression ဖြစ်ပြီး arm တွေက range၊ tuple၊ enum နဲ့ condition တွေကို match လုပ်နိုင်ကာ fall-through မရှိလို့ မေ့စရာ break မရှိပါဘူး။
  7. _ က ဘာကိုမဆို match လုပ်ပါတယ်။ match က exhaustive ဖြစ်ရပါမယ် — ဒီ arm ကို ဖယ်လိုက်ရင် ဘယ်ဂဏန်းတွေ မပါသေးလဲ compiler က ပြောပါလိမ့်မယ်။ နောက်ပိုင်း enum တွေပေါ်မှာ match က အသုံးဝင်တာ ဒီစစ်ဆေးမှုကြောင့်ပါ။

Ownership — အရာအားလုံး အခြေခံတဲ့ idea

ဒါက Rust ရဲ့ နှလုံးသားဖြစ်ပြီး JS မှာ တူညီတာ မရှိတဲ့ အပိုင်းပါ။ အရင်ဆုံး JavaScript က object တွေကို ဘယ်လို ဆက်ဆံလဲ ပြန်သတိရကြည့်ပါ—

ts
const a = { tags: ['ts'] };const b = a;           // b and a point at the same objectb.tags.push('rust');console.log(a.tags);   // ['ts', 'rust']: changed through b

တစ်ကြောင်းချင်း ရှင်းလင်းချက်

  1. Object တစ်ခုကို assign လုပ်ရင် reference ကို ကူးပါတယ်။ အခု နာမည်နှစ်ခုက object တစ်ခုတည်းကို မျှသုံးနေပြီး garbage collector က ဘယ်နာမည်ကမှ မရောက်နိုင်တော့မှ free လုပ်ပါတယ်။
  2. နာမည်တစ်ခုကနေ ပြင်တာကို နောက်တစ်ခုကနေ မြင်ရပါတယ်။ ဒီ sharing က အဆင်ပြေသလို bug အများကြီးရဲ့ အမြစ်လည်း ဖြစ်ပါတယ် — သင်ခေါ်တဲ့ function က reference ကို သိမ်းထားပြီး နောက်မှ သင့် data ကို ပြင်နိုင်ပါတယ်။

Rust က ဒါကို compiler စစ်ဆေးတဲ့ rule သုံးခုနဲ့ အစားထိုးပါတယ်—

  1. Value တိုင်းမှာ owner တစ်ယောက်တည်း၊ variable တစ်ခု ရှိပါတယ်။
  2. Owner က scope ထဲက ထွက်သွားရင် value ကို အဲဒီနေရာမှာပဲ drop လုပ် (memory ကို free လုပ်) ပါတယ်။ Garbage collector မလိုပါဘူး။
  3. Heap value တစ်ခုကို assign ဒါမှမဟုတ် pass လုပ်ရင် ownership ကို နေရာအသစ်ဆီ move လုပ်ပါတယ်။ နာမည်ဟောင်းကို ဆက်သုံးလို့ မရတော့ပါဘူး။
rustပြင်ပြီး Run ကို နှိပ်ကြည့်ပါ

Tab က indent · ⌘/Ctrl + Enter က run · Esc က editor ထဲက ထွက်

တစ်ကြောင်းချင်း ရှင်းလင်းချက်

  1. String::from က စာသားကို heap ပေါ်တင်ပြီး a ကို owner ဖြစ်စေပါတယ်။ :: က type တစ်ခုနဲ့ သက်ဆိုင်တဲ့ function ကို static method လို ခေါ်တာပါ။
  2. ဒါက shared reference မဟုတ်ဘဲ move ပါ။ Ownership က b ဆီ ကူးသွားပြီး a ကို သုံးလို့ မရတော့ပါဘူး။ Pointer၊ length နဲ့ capacity ကိုပဲ ကူးပြီး စာသားကိုယ်တိုင်က နေရာမှာပဲ ရှိနေပါတယ်။
  3. ဒီစာကြောင်းကို uncomment လုပ်ပြီး Run ပါ — move ပြီးနောက် a ကို သုံးတာကို compiler က ငြင်းပါတယ်။ JS မှာဆိုရင် object ကို တိတ်တဆိတ် မျှသုံးမှာဖြစ်ပြီး Rust မှာတော့ free လုပ်ဖို့ owner တစ်ယောက်ထက် ဘယ်တော့မှ မပိုပါဘူး။
  4. .clone() က ထင်ရှားတဲ့ deep copy လုပ်လို့ b နဲ့ c က string သီးခြားစီကို ပိုင်ပါတယ်။ Clone တွေက code ထဲမှာ မြင်ရလို့ ဘယ်နေရာမှာ ကူးတာ ကုန်ကျလဲ မြင်နိုင်ပါတယ်။
  5. ပုံသေ size ရှိတဲ့ type အသေးလေးတွေ (integer၊ float၊ bool၊ char နဲ့ အဲဒါတွေရဲ့ tuple) က Copy ပါ — assign လုပ်ရင် bit တွေကို ကူးပြီး နာမည်နှစ်ခုလုံး ဆက်သုံးလို့ ရပါတယ်။ Move က heap memory လို တစ်ခုခုကို ပိုင်တဲ့ value တွေအတွက်ပဲ အရေးပါပါတယ်။
  6. String ကို function ဆီ pass လုပ်ရင် parameter s ထဲ move ဖြစ်သွားပါတယ်။ take return ပြန်တဲ့အခါ s ကို drop လုပ်ပြီး memory ကို free လုပ်ပါတယ်။ ဒီစာကြောင်းနောက် c ကို သုံးရင် error ဖြစ်ပါလိမ့်မယ်။
  7. Value ကို return ပြန်ရင် ownership က ခေါ်သူဆီ move ဖြစ်လို့ give က string ဖန်တီးပြီး d က သူ့ owner ဖြစ်လာပါတယ်။
  8. Closing brace မှာ b နဲ့ d က scope ထဲက ထွက်ပြီး သူတို့ string တွေကို ဖန်တီးခဲ့တဲ့ အစဉ်ရဲ့ ပြောင်းပြန်နဲ့ free လုပ်ပါတယ်။ ဒါက ကြိုတင်သိနိုင်ပါတယ် — memory ဘယ်အချိန် လွှတ်လဲ (file တွေ၊ socket တွေကိုလည်း ဒီနည်းအတိုင်း free လုပ်လို့ ဘယ်အချိန် ပိတ်လဲ) တိတိကျကျ သိပါတယ်။

Borrowing — ownership မယူဘဲ reference တွေ

အရာအားလုံးကို function တွေထဲ move လုပ်နေရရင် ပင်ပန်းမှာပါ။ အဲဒီအစား borrow လုပ်ပါတယ် — &value က function ကို ဖတ်ဖို့ reference ပေးပြီး &mut value က ပြင်ဖို့ reference ပေးပါတယ်။ Borrow checker က rule တစ်ခုတည်းကို ကျင့်သုံးပါတယ် — ဘယ်အချိန်မှာမဆို shared & reference ဘယ်နှစ်ခုမဆို ဒါမှမဟုတ် &mut reference တစ်ခုတည်း ရှိနိုင်ပြီး နှစ်မျိုးလုံး တစ်ပြိုင်နက် ဘယ်တော့မှ မရှိရပါဘူး။

rustပြင်ပြီး Run ကို နှိပ်ကြည့်ပါ

Tab က indent · ⌘/Ctrl + Enter က run · Esc က editor ထဲက ထွက်

တစ်ကြောင်းချင်း ရှင်းလင်းချက်

  1. &String က shared reference ပါ — length က string ကို ဖတ်လို့ရပေမဲ့ ပြင်လို့ မရဘဲ ပိုင်လည်း မပိုင်လို့ return ပြန်တဲ့အခါ ဘာမှ free မလုပ်ပါဘူး။ (Idiomatic code မှာ ဒီနေရာမှာ &str ကို ယူပါတယ်၊ နောက်အပိုင်းမှာ ကြည့်ပါ။)
  2. &mut String က mutable reference ပါ — shout က ခေါ်သူရဲ့ string ကို နေရာမှာပဲ ပြင်နိုင်ပါတယ်။
  3. &word က call ကြာသရွေ့ string ကို ငှားပေးပါတယ်။ ပြီးရင် word က ကျွန်တော်တို့ ဆီမှာပဲ ရှိနေဆဲပါ။
  4. Mutable အနေနဲ့ ငှားဖို့ variable (let mut word) နဲ့ borrow (&mut word) နှစ်ခုလုံးက ပြောရပါမယ်။ ဘယ် function တွေက သူတို့ data ကို ပြင်နိုင်လဲဆိုတာ JS က ဘယ်တော့မှ မပြောပေမဲ့ ဒီမှာ call site မှာတင် ဖတ်သူ မြင်နိုင်ပါတယ်။
  5. ဖတ်သူတွေက တစ်ယောက်နဲ့တစ်ယောက် မနှောင့်ယှက်နိုင်လို့ shared borrow ဘယ်နှစ်ခုမဆို အတူ ရှိနိုင်ပါတယ်။
  6. r1 နဲ့ r2 ကို println! နောက်မှာ မသုံးတော့လို့ ဒီမှာ mutable borrow ခွင့်ပြုပါတယ်။ Compiler က reference တစ်ခုချင်းကို ကြေညာထားတဲ့ block ကိုပဲ မဟုတ်ဘဲ နောက်ဆုံး ဘယ်မှာ သုံးလဲဆိုတာကို လိုက်ကြည့်ပါတယ် (ဒါကို non-lexical lifetimes လို့ ခေါ်ပါတယ်)။

အခု borrow checker က "မရဘူး" လို့ ပြောတာကို တွေ့ကြည့်ရအောင်။ ဒီ program က တမင် compile မဖြစ်အောင် ရေးထားတာပါ။ Run ကို နှိပ်ပြီး error ကို ဖတ်ကြည့်ပါ၊ ပြီးရင် မှတ်စုထဲက ပြင်နည်းကို စမ်းကြည့်ပါ။

rustပြင်ပြီး Run ကို နှိပ်ကြည့်ပါ

Tab က indent · ⌘/Ctrl + Enter က run · Esc က editor ထဲက ထွက်

တစ်ကြောင်းချင်း ရှင်းလင်းချက်

  1. first က vector ရဲ့ ပထမ element ကို borrow လုပ်ပါတယ်၊ vector ရဲ့ heap buffer ထဲကို ညွှန်တဲ့ pointer တစ်ခုပါ။
  2. push က &mut list လိုပါတယ်။ Buffer ပြည့်နေရင် push က ပိုကြီးတဲ့ buffer အသစ် allocate လုပ်ပြီး element တွေကို ကူးကာ buffer ဟောင်းကို free လုပ်ပါတယ်။ ဒါဆိုရင် first က free လုပ်ပြီးသား memory ကို ညွှန်နေမှာပါ — C မှာ ဒါက ဂန္ထဝင် use-after-free bug ပါ။
  3. push နောက်မှာ first ကို သုံးနေလို့ shared borrow နဲ့ mutable borrow ထပ်နေပြီး compiler က ငြင်းပါတယ် (error E0502)။ ပြင်ဖို့ let first = list[0]; နဲ့ value ကို ကူးထုတ်ပါ (i32 က Copy ပါ)၊ ဒါမှမဟုတ် println! ကို push အပေါ် ရွှေ့ပါ။

String နဲ့ &str

Rust မှာ အဓိက string type နှစ်ခု ရှိပြီး အစပိုင်းမှာ လူတိုင်း ရှုပ်ကြပါတယ်။ String က StringBuilder လို ပိုင်ဆိုင်ထားတဲ့၊ ကြီးထွားနိုင်တဲ့ buffer ပါ။ &str ("string slice") ကတော့ တခြားတစ်ယောက် ပိုင်တဲ့ စာသားထဲကို borrow လုပ်ထားတဲ့ မြင်ကွင်းပါ — literal တစ်ခု၊ String ရဲ့ အစိတ်အပိုင်း ဒါမှမဟုတ် တစ်ခုလုံး။ နှစ်ခုလုံး အမြဲ valid UTF-8 ပါ။

rustပြင်ပြီး Run ကို နှိပ်ကြည့်ပါ

Tab က indent · ⌘/Ctrl + Enter က run · Esc က editor ထဲက ထွက်

တစ်ကြောင်းချင်း ရှင်းလင်းချက်

  1. ဖတ်ရုံပဲ ဖတ်မယ့် parameter တွေအတွက် &str ကို ယူပါ — literal၊ String နဲ့ slice အားလုံးကို လက်ခံပါတယ်။ စာသားအသစ် တည်ဆောက်တဲ့အခါ ခေါ်သူက ပိုင်ဖို့ လိုလို့ String ကို return ပြန်ပါ။
  2. format! က println! လိုပဲ အလုပ်လုပ်ပေမဲ့ print မထုတ်ဘဲ String ကို return ပြန်ပါတယ်။ Rust ရဲ့ template literal ပါ။
  3. Literal တစ်ခုက program binary ထဲကို ညွှန်တဲ့ &str ပါ။
  4. String က heap ပေါ်မှာ နေပြီး ကြီးထွားနိုင်ပါတယ်။ String::from(…)၊ "…".to_string() နဲ့ "…".to_owned() အားလုံး တစ်ခု ဖန်တီးပေးပါတယ်။
  5. push_str က နေရာမှာပဲ ဆက်ထည့်လို့ owned က mut ဖြစ်ရတာပါ။ Emoji တွေ ရပါတယ် — string တွေက UTF-8 ပါ။
  6. &owned က String ကို borrow လုပ်ပြီး Rust က &String ကို &str အဖြစ် အလိုအလျောက် ပြောင်းပေးပါတယ် (deref coercion)။ &str parameter တွေက နှစ်မျိုးလုံး လက်ခံတာ ဒါကြောင့်ပါ။
  7. Slicing က byte range ကို ယူပါတယ် — 0..5 က "Minga" ပါ။ Byte အများကြီးနဲ့ ဖွဲ့ထားတဲ့ character အလယ်မှာ slice လုပ်ရင် panic ဖြစ်လို့ ASCII မဟုတ်တဲ့ စာသားအတွက် ပုံသေ ဂဏန်းတွေအစား chars() ဒါမှမဟုတ် ရှာဖွေမှုနဲ့ အလုပ်လုပ်ပါ။
  8. မြန်မာ character တွေက UTF-8 မှာ တစ်လုံးကို သုံး byte စီ ယူလို့ len() (byte) နဲ့ chars().count() (Unicode scalar value) မတူပါဘူး — 27 နဲ့ 9 ပါ။ JavaScript ရဲ့ .length က UTF-16 code unit တွေကို ရေတွက်လို့ တတိယ အဖြေတစ်ခု ထပ်ရပါတယ်။ ကိုယ်တကယ် ဆိုလိုတာကို ရေတွက်ပါ။

Lifetime တွေ အကျဉ်းချုပ်

Lifetime ဆိုတာ "reference တစ်ခု ဘယ်လောက်ကြာကြာ valid ဖြစ်နေလဲ" ဆိုတာအတွက် compiler ပေးတဲ့ နာမည်ပါ။ အများအားဖြင့် ကိုယ့်ဘာသာ တွက်ထုတ်ပါတယ်။ Function တစ်ခုက reference ကို return ပြန်ပြီး ဘယ် input ကလာလဲ compiler မသိနိုင်တဲ့အခါမှပဲ ရေးရပါတယ်။

rustပြင်ပြီး Run ကို နှိပ်ကြည့်ပါ

Tab က indent · ⌘/Ctrl + Enter က run · Esc က editor ထဲက ထွက်

တစ်ကြောင်းချင်း ရှင်းလင်းချက်

  1. <'a> က generic လို lifetime parameter တစ်ခု ကြေညာပါတယ်။ Signature က ပြောတာက — result က x နဲ့ y ထဲက သက်တမ်းတိုတဲ့ တစ်ခုထက် ပိုမကြာပါဘူး။ ဘယ်အရာ ဘယ်လောက်ကြာကြာ အသက်ရှင်လဲကို မပြောင်းပါဘူး — ခေါ်သူကို စစ်နိုင်အောင် ဆက်နွယ်မှုကို ဖော်ပြတာပါ။
  2. Reference ကို ကိုင်ထားတဲ့ struct က အဲဒီ reference ဘယ်လောက်ကြာ အသက်ရှင်လဲ ပြောရပါတယ် — Excerpt တစ်ခုက သူညွှန်ထားတဲ့ စာသားထက် ပိုကြာကြာ မနေနိုင်ပါဘူး။
  3. b က ဒီ အတွင်း block ထဲမှာပဲ ရှိပြီး closing brace မှာ free ဖြစ်ပါတယ်။
  4. longest က b ထဲကို reference ပြန်ပေးနိုင်လို့ အဲဒီ reference ကို brace ကျော်ပြီး ထိန်းထားရင် error ဖြစ်ပါလိမ့်မယ်။ .to_string() က result ကို ဆက်ရှင်သန်နိုင်တဲ့ owned String အသစ်ထဲ ကူးပါတယ်။ Error ကို မြင်ဖို့ .to_string() ကို ဖယ်ကြည့်ပါ။
  5. split က text ကနေ borrow လုပ်ထားတဲ့ &str slice တွေကို ကူးစရာမလိုဘဲ ထုတ်ပေးပါတယ်။ next() က ပထမတစ်ခုကို Option အဖြစ် ယူပြီး unwrap() က ထုတ်ယူပါတယ်။

Struct တွေနဲ့ method တွေ

Rust မှာ class မရှိပါဘူး။ Data ကို struct ထဲ ထည့်ပြီး behaviour ကို impl block တွေထဲ ထည့်ပါတယ်။ Inheritance မရှိပါဘူး — behaviour မျှသုံးတာက အောက်မှာ ပြောမယ့် trait တွေရဲ့ အလုပ်ပါ။

ts
class Account {  constructor(public owner: string, private balance = 0) {}  deposit(amount: number) { this.balance += amount; }  getBalance() { return this.balance; }}
rustပြင်ပြီး Run ကို နှိပ်ကြည့်ပါ

Tab က indent · ⌘/Ctrl + Enter က run · Esc က editor ထဲက ထွက်

တစ်ကြောင်းချင်း ရှင်းလင်းချက်

  1. #[derive] က compiler ကို standard trait implementation တွေ ထုတ်ပေးဖို့ တောင်းတာပါ — {:?} နဲ့ print ထုတ်ဖို့ Debug၊ .clone() အတွက် Clone နဲ့ == အတွက် PartialEq။ JS မှာ object ပေါ်က == က reference တွေကို နှိုင်းယှဉ်ပေမဲ့ Rust မှာတော့ အကြောင်းအရာကို နှိုင်းယှဉ်ပြီး သင်တောင်းမှပဲ ရပါတယ်။
  2. Struct ဆိုတာ နာမည်ပေးထားတဲ့ type ပါ field အစုတစ်ခုပါ၊ memory layout ကိုပါ ဆုံးဖြတ်တဲ့ TS interface လိုပေါ့။ pub လို့ မမှတ်ထားရင် field တွေက module အတွက် private ပါ။
  3. ငွေကို အသေးဆုံး ယူနစ် (cent၊ ပြား) နဲ့ unsigned integer အဖြစ် သိမ်းပါ — အနုတ် မဖြစ်နိုင်ဘဲ float ရဲ့ rounding အံ့သြစရာတွေ မရှိပါဘူး။
  4. impl block က type တစ်ခုရဲ့ method တွေကို ကိုင်ထားပါတယ်။ Crate တစ်ခုတည်းရဲ့ file မတူတာတွေထဲမှာတောင် အများကြီး ရှိနိုင်ပါတယ်။
  5. self မပါတဲ့ function က associated function ဖြစ်ပြီး Rust ရဲ့ static method ပါ။ new က keyword မဟုတ်ဘဲ ထုံးစံတစ်ခုပဲ ဖြစ်ပါတယ် — constructor မရှိပါဘူး။ Self က implement လုပ်နေတဲ့ type ကို ဆိုလိုပါတယ်။
  6. &mut self က method က struct ကို mutable အနေနဲ့ borrow လုပ်တာမို့ mut variable ပေါ်မှာပဲ ခေါ်လို့ ရပါတယ်။ ဘယ် method တွေက value ကို ပြောင်းလဲလဲ signature က ပြောပါတယ်။
  7. &self က read-only borrow လုပ်ပါတယ်။ Field နဲ့ method က နာမည်တူနိုင်ပါတယ် — acc.balance() က method ကို ခေါ်ပြီး acc.balance က field ကို ဖတ်ပါတယ်။
  8. Associated function တွေကို :: နဲ့၊ method တွေကို . နဲ့ ခေါ်ပါ။ acc.deposit(…) အတွက် Rust က &mut acc ကို အလိုအလျောက် borrow လုပ်ပေးပါတယ်။
  9. {:?} (ဒီမှာ {acc:?}) က Debug ပုံစံကို print ထုတ်ပါတယ်၊ object တစ်ခုကို console.log လုပ်သလို develop လုပ်နေတုန်း အသုံးဝင်ပါတယ်။ {:#?} က စာကြောင်းအများကြီးနဲ့ လှလှပပ print ထုတ်ပါတယ်။
  10. Struct update syntax ..acc.clone() က ကျန်တဲ့ field တွေကို တခြား value တစ်ခုကနေ ဖြည့်ပါတယ်၊ JS ရဲ့ { ...acc, owner: "Grace" } လိုပေါ့။ မ clone ရင် spread က acc ရဲ့ field တွေကို move လုပ်သွားမှာမို့ clone လုပ်ပါတယ်။

Enum တွေနဲ့ pattern matching

TypeScript မှာ discriminated union တွေ သုံးဖူးရင် Rust enum တွေကို နားလည်ပြီးသားပါ။ Variant တစ်ခုစီက ကိုယ်ပိုင် data ကို သယ်နိုင်ပြီး match က တစ်ခုချင်းစီကို ကိုင်တွယ်ကြောင်း သေချာစေပါတယ်။

ts
type Shape =  | { kind: 'circle'; radius: number }  | { kind: 'rect'; w: number; h: number }; function area(s: Shape): number {  switch (s.kind) {    case 'circle': return Math.PI * s.radius ** 2;    case 'rect': return s.w * s.h;  }}

တစ်ကြောင်းချင်း ရှင်းလင်းချက်

  1. TypeScript က kind tag ပေါ်မှာ narrow လုပ်ပါတယ်။ ကျန်နေတဲ့ case ကို ဖမ်းမဖမ်းက သင့် compiler setting နဲ့ return type ပေါ် မူတည်ပါတယ် — Rust မှာတော့ အမြဲ ဖမ်းပါတယ်။
rustပြင်ပြီး Run ကို နှိပ်ကြည့်ပါ

Tab က indent · ⌘/Ctrl + Enter က run · Esc က editor ထဲက ထွက်

တစ်ကြောင်းချင်း ရှင်းလင်းချက်

  1. enum က value တစ်ခုရဲ့ ဖြစ်နိုင်တဲ့ ပုံစံအားလုံးကို စာရင်းပြုစုပါတယ်။ Tag က ပါပြီးသားမို့ kind field ကို ကိုယ်တိုင် ဘယ်တော့မှ မရေးရပါဘူး။
  2. Variant တစ်ခုက struct လို နာမည်ပါ field တွေ၊ tuple လို နာမည်မပါ field တွေ (Circle(f64)) ဒါမှမဟုတ် ဘာမှမပါဘဲ သယ်နိုင်ပါတယ်။
  3. Data မပါတဲ့ variant က TS string-literal union ရဲ့ member တစ်ခုလိုပါ။
  4. match က variant တစ်ခုစီကို destructure လုပ်ပြီး သူ့ field တွေကို variable အဖြစ် bind လုပ်ပါတယ်။ Exhaustive ဖြစ်ပါတယ် — Triangle variant တစ်ခု ထည့်လိုက်ရင် အဲဒါကို မေ့ထားတဲ့ match တိုင်း compile မဖြစ်တော့ပါဘူး။ "Case အသစ်ကို ကိုင်တွယ်ဖို့ မေ့သွားတယ်" ဆိုတာကို production bug ကနေ compile error အဖြစ် ပြောင်းပေးပါတယ်။
  5. Enum တွေရဲ့ array ပါ။ Variant တွေက မတူတဲ့ data သယ်ပေမဲ့ element တိုင်းမှာ type တူ — Shape ပါ။
  6. {:.2} က float ကို ဒသမ နှစ်နေရာနဲ့ print ထုတ်ပါတယ်၊ toFixed(2) လိုပေါ့။
  7. match က enum တွေတင်မက ဘယ် value ပေါ်မှာမဆို အလုပ်လုပ်ပါတယ်။
  8. Match guard (if …) က arm တစ်ခုကို condition ထပ်ထည့်ပါတယ်။ Guard က သုံးနိုင်အောင် x က match ဖြစ်တဲ့ value ကို bind လုပ်ပါတယ်။

null နဲ့ undefined အစား Option

Tony Hoare က null reference တွေကို သူ့ရဲ့ "ဒေါ်လာ ဘီလီယံတစ်ထောင်တန် အမှား" လို့ ခေါ်ခဲ့ပါတယ်။ Rust မှာ null မရှိပါဘူး။ မရှိနိုင်တဲ့ value မှာ Some(value) နဲ့ None ဆိုတဲ့ variant နှစ်ခုပါတဲ့ enum ဖြစ်တဲ့ Option<T> type ရှိပြီး value ကို မသုံးခင် None ကို ကိုင်တွယ်ရပါတယ်။

rustပြင်ပြီး Run ကို နှိပ်ကြည့်ပါ

Tab က indent · ⌘/Ctrl + Enter က run · Esc က editor ထဲက ထွက်

တစ်ကြောင်းချင်း ရှင်းလင်းချက်

  1. User မရှိနိုင်ဘူးဆိုတာကို TS မှာ string | undefined က ပြောသလို return type က ရိုးရိုးသားသား ပြောပါတယ်။ ကွာခြားချက်က Option<&str> က &str မဟုတ်လို့ string method တွေကို မတော်တဆ ခေါ်လို့ မရပါဘူး။
  2. Variant နှစ်ခုလုံးကို match လုပ်တာက ကိုင်တွယ်ဖို့ အထင်ရှားဆုံး နည်းပါ။
  3. if let က narrowing ပါတဲ့ if (user !== undefined) လို ကိုယ်ဂရုစိုက်တဲ့ case ကိုပဲ ကိုင်တွယ်ပါတယ်။
  4. unwrap_or က JS ရဲ့ ?? "guest" လို default ပေးပါတယ်။
  5. map က Some ထဲက value ကို ပြောင်းပြီး None ကို မထိပါဘူး၊ optional chaining user?.length လိုပေါ့။ |n| n.len() က closure ဖြစ်ပြီး Rust ရဲ့ arrow function (n) => n.length ပါ။
  6. Some(3) ကို print ထုတ်ပါတယ် — Debug ပုံစံက wrapper ကို ပြပါတယ်။
  7. let … else က unwrap လုပ် ဒါမှမဟုတ် ထွက်ပါတယ် — pattern မကိုက်ရင် else block က ထွက်ရပါမယ် (return၊ break ဒါမှမဟုတ် panic)။ စောစော return ပြန်တဲ့ guard clause ရဲ့ Rust version ပါ။

Result နဲ့ ? သုံးပြီး error ကိုင်တွယ်ခြင်း

JS မှာ ဘယ် function မဆို throw လုပ်နိုင်ပြီး သူ့ signature ထဲမှာ ဘာမှ မပြောပါဘူး။ Rust မှာ fail ဖြစ်နိုင်တဲ့ function က Ok(value) ဒါမှမဟုတ် Err(error) ဆိုတဲ့ enum ဖြစ်တဲ့ Result<T, E> ကို return ပြန်ပါတယ်။ ? operator က error တွေကို character တစ်လုံးတည်းနဲ့ အပေါ်ဆီ လွှဲပေးလို့ exception ရဲ့ အဆင်ပြေမှုကို error တွေ type ထဲမှာ စာရင်းပါတဲ့ ပုံစံနဲ့ ရပါတယ်။

ts
function parsePort(s: string): number {  const n = Number(s);  if (!Number.isInteger(n) || n < 1 || n > 65535) throw new Error('bad port: ' + s);  return n;}

တစ်ကြောင်းချင်း ရှင်းလင်းချက်

  1. parsePort ရဲ့ type ထဲမှာ throw လုပ်နိုင်ကြောင်း ဘာမှ ခေါ်သူကို မပြောလို့ try/catch ကို မေ့ဖို့ လွယ်ပြီး run နေချိန်မှပဲ ပေါ်လာပါတယ်။
rustပြင်ပြီး Run ကို နှိပ်ကြည့်ပါ

Tab က indent · ⌘/Ctrl + Enter က run · Esc က editor ထဲက ထွက်

တစ်ကြောင်းချင်း ရှင်းလင်းချက်

  1. use က import လို နာမည်တွေကို scope ထဲ ယူလာပါတယ်။ std::fmt က module path ဖြစ်ပြီး :: က သူ့အစိတ်အပိုင်းတွေကို ခွဲပါတယ်။
  2. ကိုယ်ပိုင် error type က ဒီ code fail ဖြစ်နိုင်တဲ့ နည်းလမ်းတိုင်းကို စာရင်းပြုစုထားတဲ့ enum ပါ။ Variant တစ်ခုက standard library ရဲ့ parse error ကို ထုပ်ထားပြီး နောက်တစ်ခုက မှားနေတဲ့ ဂဏန်းကို သယ်ပါတယ်။
  3. Display trait ကို implement လုပ်တာက error ကို {} နဲ့ ဘယ်လို print ထုတ်မလဲ ဆုံးဖြတ်ပါတယ်။ write! က formatter ဆီ ချိန်ထားတဲ့ format! ပါ။ (တကယ့် project တွေမှာ thiserror crate က variant တစ်ခုစီအတွက် attribute တစ်ခုနဲ့ ဒီ impl ကို ထုတ်ပေးပါတယ်။)
  4. From impl က ParseIntError ကို ကျွန်တော်တို့ ConfigError အဖြစ် အလိုအလျောက် ဘယ်လို ပြောင်းမလဲ ? ကို ပြောပါတယ်။
  5. Signature က "u16 တစ်ခု ဒါမှမဟုတ် ConfigError တစ်ခု" လို့ ပြောပါတယ်။ ခေါ်သူတွေက နှစ်ခုလုံးကို ကိုင်တွယ်ရပါမယ် — Result ကို လျစ်လျူရှုရင် compiler warning ပေးပါတယ်။
  6. ? operator — parse() က Err ပြန်ရင် error ကို From နဲ့ ပြောင်းပြီး ဒီ function ကနေ ချက်ချင်း return ပြန် ပါတယ်၊ Ok ဆိုရင် value ကို ထုတ်ယူပါတယ်။ try/catch/rethrow တစ်ခုလုံးကို အစားထိုးပါတယ်။ Target type u32 က parse ကို ဘာအဖြစ် parse လုပ်ရမလဲ ပြောပါတယ်။
  7. Error ကို စောစော return ပြန်တာက ရိုးရိုး control flow ပါ — Err(…) က value တစ်ခုပဲ ဖြစ်ပါတယ်။
  8. အောင်မြင်ရင် Ok နဲ့ ထုပ်ပါတယ်။ အပေါ်မှာ range ကို စစ်ပြီးသားမို့ as u16 က safe ဖြစ်ပါတယ်။
  9. Result ပေါ်မှာ match လုပ်ရင် ရလဒ်နှစ်မျိုးလုံးကို ကိုင်တွယ်ပါတယ်။ Input သုံးခုက လမ်းကြောင်း မတူတာကို သတိထားကြည့်ပါ။ Err ဖြစ်ရင် crash ဖြစ်တဲ့ unwrap() နဲ့ expect("message") လည်း ရှိပါတယ် — ဥပမာတွေ၊ test တွေမှာ ရပေမဲ့ production လမ်းကြောင်းမှာတော့ code smell ပါ။

Trait တွေနဲ့ generic တွေ

Trait ဆိုတာ type တစ်ခုက ရှိမယ်လို့ ကတိပြုတဲ့ method အစုပါ၊ TS interface လိုပေါ့။ ကွာခြားချက် — TS မှာ ပုံစံမှန်တဲ့ object ဘယ်ဟာမဆို interface နဲ့ ကိုက်ပါတယ် (structural typing)၊ Rust မှာတော့ တစ်ယောက်ယောက်က impl Trait for Type လို့ ရေးမှပဲ type က trait ကို implement လုပ်ပါတယ် (nominal typing)။ ကိုယ့် trait တွေကို သူများ type တွေအတွက်၊ သူများ trait တွေကို ကိုယ့် type တွေအတွက် implement လုပ်နိုင်ပါတယ်။

rustပြင်ပြီး Run ကို နှိပ်ကြည့်ပါ

Tab က indent · ⌘/Ctrl + Enter က run · Esc က editor ထဲက ထွက်

တစ်ကြောင်းချင်း ရှင်းလင်းချက်

  1. Trait က implement လုပ်တဲ့ type တွေ ပေးရမယ့် method signature တွေကို ကြေညာပါတယ်။
  2. Trait method မှာ mixin လို default body ရှိနိုင်ပါတယ်။ Type တွေက အလကား ရပြီး override လုပ်နိုင်ပါတယ်။
  3. impl Describe for Dog က ထင်ရှားတဲ့ opt-in ပါ။ Dog က field မပါတဲ့ unit struct ဖြစ်ပြီး type က behaviour ကိုပဲ သယ်တဲ့အခါ အသုံးဝင်ပါတယ်။ .into() က &str ကို signature လိုချင်တဲ့ String အဖြစ် ပြောင်းပါတယ်။
  4. Robot က default describe ကို override လုပ်ပါတယ်။
  5. &impl Describe ဆိုတာ "Describe ကို implement လုပ်တဲ့ ဘယ် type ရဲ့ reference မဆို" လို့ အဓိပ္ပာယ်ရပါတယ်။ ဒါက static dispatch ပါ — compiler က Dog အတွက်နဲ့ Robot အတွက် announce ရဲ့ သီးခြား copy တွေ ထုတ်လို့ call တွေက တိုက်ရိုက်ဖြစ်ပြီး inline လုပ်နိုင်ပါတယ်။
  6. Trait bound ပါတဲ့ generic function ပါ — T က > နဲ့ နှိုင်းယှဉ်လို့ရ (PartialOrd) ပြီး ကူးလို့ရ (Copy) ရပါမယ်။ TS ရဲ့ <T extends …> လိုပေမဲ့ bound တွေက စွမ်းရည် တွေကို စာရင်းပြုစုပြီး body က အဲဒါတွေကိုပဲ သုံးကြောင်း compiler က စစ်ပါတယ်။ &[T] က slice ဖြစ်ပြီး array ဒါမှမဟုတ် Vec ဘယ်ဟာရဲ့မဆို borrow လုပ်ထားတဲ့ မြင်ကွင်းပါ။
  7. dyn Describe က dynamic dispatch ပါ — JS method call လို method ကို run နေချိန်မှာ table ထဲက ရှာတဲ့ trait object ပါ။ Size မတူတဲ့ value တွေ list တစ်ခုထဲ နေနိုင်အောင် Box က value တစ်ခုစီကို heap ပေါ် တင်ပါတယ်။
  8. Type parameter အများကြီး ပါပြီး တစ်ခုစီက Display ({} နဲ့ print ထုတ်လို့ရ) နဲ့ bound လုပ်ထားပါတယ်။ String နဲ့ integer နှစ်ခုလုံး ကိုက်ပါတယ်။
  9. Trait object တွေရဲ့ Vec က မတူတဲ့ concrete type တွေကို collection တစ်ခုထဲ သိမ်းပါတယ်၊ Describe တွေရဲ့ TS array နဲ့ အနီးစပ်ဆုံးပါ။ Type annotation က compiler ကို Box<Dog> နဲ့ Box<Robot> တစ်ခုစီကို Box<dyn Describe> အဖြစ် ပြောင်းဖို့ ပြောပါတယ်။

Collection တွေ၊ iterator တွေနဲ့ closure တွေ

သင်အကြိုက်ဆုံး array method တွေ အကုန်ဒီမှာ ရှိပါတယ်၊ ကွာခြားချက် နှစ်ခုနဲ့ပေါ့ — chain တစ်ခု စဖို့ .iter() ကို ခေါ်ရပြီး iterator တွေက lazy ပါ။ တစ်ခုခုက consume မလုပ်မချင်း (sum၊ collect၊ for loop) ဘာမှ မ run ပါဘူး။ ပြီးရင် compiler က chain တစ်ခုလုံးကို ကြားထဲမှာ ယာယီ array မရှိဘဲ loop တစ်ခုတည်းအဖြစ် ပေါင်းပေးပါတယ်။

ts
const prices = [12, 5, 30, 8];const total = prices.filter((p) => p > 6).map((p) => p * 2).reduce((a, b) => a + b, 0);

တစ်ကြောင်းချင်း ရှင်းလင်းချက်

  1. JS မှာ အဆင့်တိုင်းက array အသစ် ဆောက်ပါတယ် — filter က တစ်ခု၊ map က နောက်တစ်ခု ဆောက်ပြီးမှ reduce က လျှောက်ပါတယ်။
rustပြင်ပြီး Run ကို နှိပ်ကြည့်ပါ

Tab က indent · ⌘/Ctrl + Enter က run · Esc က editor ထဲက ထွက်

တစ်ကြောင်းချင်း ရှင်းလင်းချက်

  1. Vec<T> က ကြီးထွားနိုင်တဲ့ list ဖြစ်ပြီး Rust ရဲ့ Array ပါ။ vec![…] က literal ကနေ တစ်ခု ဆောက်ပေးတဲ့ macro ပါ။ push လုပ်ဖို့ mut ဖြစ်ရပါမယ်။
  2. TS version နဲ့ chain အတူတူပါ။ .iter() က vector ကို consume မလုပ်ဘဲ element တစ်ခုချင်းကို borrow လုပ်ပြီး .sum() က chain ကို မောင်းပါတယ်။ total ပေါ်က type annotation က sum ကို ဘာထုတ်ရမလဲ ပြောပါတယ်။
  3. Closure က &&i32 (iterator ရဲ့ reference ကို ညွှန်တဲ့ reference) ကို လက်ခံပြီး pattern |&&p| က နှစ်ခုလုံးကို ခွာပါတယ်။ အဲဒီအစား |p| **p > 6 ဒါမှမဟုတ် .copied() (မှတ်စု 5 လို) ကို မကြာခဏ တွေ့ရပါလိမ့်မယ်။ Iterator code ရဲ့ အရှုပ်ဆုံး အပိုင်းဖြစ်ပြီး မှားရင် compiler က ပြောပါတယ်။
  4. collect() က iterator ကို collection တစ်ခုအဖြစ် စုပါတယ်။ အမျိုးအစား အများကြီး (Vec၊ HashMap၊ String…) ဆောက်နိုင်လို့ ဘယ်ဟာလဲဆိုတာ type annotation နဲ့ ပြောရပါတယ်။ join က JS လိုပဲ အလုပ်လုပ်ပါတယ်။
  5. .copied() က &i32 item တွေကို ရိုးရိုး i32 value တွေ အဖြစ် ပြောင်းပါတယ်။ Vec<_> က element type ကို compiler ဖြည့်ခိုင်းတာပါ။ Closure က ပတ်ဝန်းကျင် scope ထဲက threshold ကို JS closure အတိုင်းပဲ သုံးပါတယ်။
  6. HashMap က Map ပါ။ Key နဲ့ value တွေမှာ type ရှိပါတယ်။ အစဉ်လိုက် မဟုတ်ပါဘူး — iterate လုပ်တဲ့ အစဉ်ကို သတ်မှတ်မထားလို့ အားမကိုးပါနဲ့။
  7. Entry API — key တစ်ခုရဲ့ value ကို ယူ၊ မရှိရင် 0 ထည့်ပြီး နေရာမှာပဲ ပေါင်းပါတယ်။ * က or_insert ပြန်ပေးတဲ့ &mut u32 ကို dereference လုပ်ပါတယ်။ JS ရဲ့ map.set(k, (map.get(k) ?? 0) + 5) အစား lookup တစ်ခါတည်းပါ။
  8. တည်ငြိမ်တဲ့ output အတွက် key တွေကို စုပြီး sort လုပ်ပါ။ sort က နေရာမှာပဲ sort လုပ်ပါတယ်၊ ပြောင်းပြန် အစဉ်အတွက် sort_by(|a, b| b.cmp(a)) ရှိပါတယ်။
  9. Key မရှိနိုင်လို့ get က Option ပြန်ပေးပါတယ်။ stock["plums"] ဆိုရင်တော့ panic ဖြစ်ပါလိမ့်မယ်။
  10. ဒီစာကြောင်းမှာ ဘာမှ မ run သေးပါဘူး — lengths က လုပ်ရမယ့်အလုပ်ရဲ့ ဖော်ပြချက် သက်သက်ပါ။ max() က consume လုပ်ပြီး ဗလာ iterator မှာ အမြင့်ဆုံး မရှိလို့ Option<usize> ပြန်ပေးပါတယ်။

Module တွေ၊ crate တွေနဲ့ visibility

ES module တစ်ခုက file တစ်ခုပါ။ Rust module ကတော့ mod နဲ့ ကြေညာတဲ့ namespace ဖြစ်ပြီး inline ဒါမှမဟုတ် သူ့ကိုယ်ပိုင် file ထဲမှာ ရှိနိုင်ပါတယ်။ အရာအားလုံးက default အနေနဲ့ private ဖြစ်ပြီး pub က export လုပ်ပေးပါတယ်။

rustပြင်ပြီး Run ကို နှိပ်ကြည့်ပါ

Tab က indent · ⌘/Ctrl + Enter က run · Esc က editor ထဲက ထွက်

တစ်ကြောင်းချင်း ရှင်းလင်းချက်

  1. mod geometry { … } က module ကို inline ကြေညာပါတယ်။ တကယ့် project မှာ mod geometry; လို့ ရေးပြီး Cargo က src/geometry.rs (ဒါမှမဟုတ် src/geometry/mod.rs) ကို ဖတ်ပါတယ်။
  2. Struct ပေါ်က pub က type ကို module အပြင်ကနေ မြင်ရစေပြီး field တစ်ခုစီပေါ်က pub က အဲဒီ field ကို ဖတ်/ရေးလို့ ရစေပါတယ်။ pub x မပါရင် အပြင်က code က Point ကို ကိုင်ထားနိုင်ပေမဲ့ Point { x, y } နဲ့ ဆောက်လို့ မရပါဘူး — invariant တွေ ထိန်းပုံက ဒါပါ — field တွေကို ဖုံးပြီး စစ်ဆေးပေးတဲ့ new ကို ပေးပါ။
  3. pub မပါ — square က geometry နဲ့ သူ့ child တွေအတွက် private ပါ၊ JS module က export မလုပ်တဲ့ function လိုပေါ့။
  4. Module တွေ အဆင့်ဆင့် ထပ်နိုင်ပါတယ်။ geometry::shapes က child module ပါ။
  5. super က parent module ပါ၊ import path ထဲက ../ လိုပေါ့။ crate:: က crate ရဲ့ root ကနေ စတာဖြစ်ပြီး absolute path လိုပါ။
  6. use က path တစ်ခုကို scope ထဲ ယူလာပါတယ်၊ import { Point } from "./geometry" လိုပေါ့။ တခြား crate တွေလည်း ဒီအတိုင်းပါ — cargo add rand ပြီးရင် use rand::Rng; လို့ ရေးပါတယ်။

Disk ပေါ်မှာ အဲဒီ crate ကိုပဲ file တွေခွဲထားရင် ဒီလိုပုံရပါတယ်။ index.ts မရှိပါဘူး — child တွေကို ထည့်ပေးတာက parent file ထဲက mod စာကြောင်းတွေပါ။

text
Cargo.tomlsrc/├── main.rs          mod geometry;    ← includes src/geometry.rs├── lib.rs           (optional: the library half of the crate)├── geometry.rs      pub mod shapes;  ← includes src/geometry/shapes.rs└── geometry/    └── shapes.rstests/└── api.rs           integration tests, compiled as a separate crate

တစ်ကြောင်းချင်း ရှင်းလင်းချက်

  1. main.rs က binary crate ရဲ့ root ဖြစ်ပြီး lib.rs က library crate ရဲ့ root ပါ။ ဘယ် mod စာကြောင်းကမှ မဖော်ပြတဲ့ file ကို compile မလုပ်ပါဘူး — မသုံးတဲ့ file တစ်ခုမှ build ထဲ ခိုးမဝင်နိုင်ပါဘူး။
  2. Package တစ်ခုက library နဲ့ binary ကို တွဲထားနိုင်ပါတယ်။ ဒါဆိုရင် binary က library ကို တခြား dependency တစ်ခုလိုပဲ use your_crate_name::… နဲ့ သုံးပါတယ်။
  3. tests/ ထဲက file တွေက သုံးစွဲသူ တစ်ယောက်လိုပဲ သင့် crate ရဲ့ public API ကိုပဲ သုံးနိုင်ပါတယ်။ Unit test တွေကတော့ နောက်မှာ ပြမယ့်အတိုင်း code ဘေးမှာ နေပါတယ်။

Testing က ပါပြီးသား

Jest မလို၊ Vitest မလို၊ config မလိုပါဘူး — function တစ်ခုကို #[test] လို့ မှတ်လိုက်ရင် cargo test က run ပေးပါတယ်။ ဒီ block မှာ Run ကို နှိပ်ကြည့်ပါ — test တွေပါပြီး main မပါတဲ့ file ကို ဒီမှာလည်း cargo test အဖြစ် run ပါတယ်။

rustပြင်ပြီး Run ကို နှိပ်ကြည့်ပါ

Tab က indent · ⌘/Ctrl + Enter က run · Esc က editor ထဲက ထွက်

တစ်ကြောင်းချင်း ရှင်းလင်းချက်

  1. Test လုပ်မယ့် function ပါ၊ တခြား module တွေ (test module အပါအဝင်) သုံးနိုင်အောင် pub ထားပါတယ်။
  2. split က ဘယ် character တွေက ခွဲတဲ့ နေရာလဲ ဆုံးဖြတ်တဲ့ closure ကို လက်ခံပါတယ်။ split က pattern အမျိုးအစား အများကြီး လက်ခံလို့ ဒီမှာ c: char ရဲ့ type ကို ရေးရပါတယ်။
  3. "Turbofish" ::<Vec<_>> က variable ကို annotate မလုပ်ဘဲ collect ရဲ့ target type ကို inline ပေးပါတယ်။
  4. #[cfg(test)] က ဒီ module ကို cargo test အတွက်ပဲ compile လုပ်လို့ test တွေနဲ့ test helper တွေက release binary ထဲ ဘယ်တော့မှ မရောက်ပါဘူး။
  5. Test module က child ဖြစ်လို့ super::* က private function တွေအပါအဝင် parent ထဲက အရာအားလုံးကို import လုပ်ပါတယ်။ Rust မှာ private code ကို တိုက်ရိုက် unit-test လုပ်နိုင်ပါတယ်။
  6. #[test] က test တစ်ခုကို မှတ်ပါတယ်။ Test တွေကို default အနေနဲ့ parallel thread တွေမှာ run လို့ တစ်ခုနဲ့တစ်ခု မမှီခိုရပါဘူး။
  7. assert_eq! က နှိုင်းယှဉ်ပြီး fail ဖြစ်ရင် ဘက်နှစ်ဖက်လုံးကို print ထုတ်ပါတယ်။ assert!(condition) နဲ့ assert_ne! လည်း ရှိပါတယ်။ မျှော်လင့်ထားတဲ့ string ကို ဖျက်ဆီးပြီး ပြန် Run ကြည့်ပါ၊ failure report ကို မြင်ရပါလိမ့်မယ်။
  8. #[should_panic] က body က panic ဖြစ်မှပဲ pass ဖြစ်ပါတယ် — မမှန်တဲ့ input ကို ငြင်းကြောင်း test လုပ်ပုံပါ။

Smart pointer တွေ — Box၊ Rc နဲ့ RefCell

တစ်ခါတလေ owner တစ်ယောက်တည်း မလုံလောက်ပါဘူး၊ ဒါမှမဟုတ် value တစ်ခုက heap ပေါ်မှာ နေရပါတယ်။ အဲဒီ case တွေအတွက် standard library မှာ smart pointer အနည်းငယ် ရှိပါတယ်။ တစ်ခုစီက heap memory အချို့ကို ပိုင်ပြီး drop ဖြစ်တဲ့အခါ free လုပ်တဲ့ ရိုးရိုး struct တစ်ခုပါ။

rustပြင်ပြီး Run ကို နှိပ်ကြည့်ပါ

Tab က indent · ⌘/Ctrl + Enter က run · Esc က editor ထဲက ထွက်

တစ်ကြောင်းချင်း ရှင်းလင်းချက်

  1. Recursive type က indirection လိုပါတယ် — List တစ်ခုက List တစ်ခုလုံးကို တိုက်ရိုက် ထည့်ထားလို့ မရပါဘူး၊ ထည့်ရင် size က အဆုံးမရှိ ဖြစ်သွားမှာပါ။ Box<List> က heap ပေါ် allocate လုပ်ထားတဲ့ List ကို ညွှန်တဲ့ size သိပြီးသား pointer ပါ။ Box က အရိုးရှင်းဆုံး smart pointer ပါ — heap ပေါ်က တစ်ခုခုရဲ့ owner တစ်ယောက်တည်းပေါ့။
  2. Box က အလိုအလျောက် dereference ဖြစ်လို့ rest (&Box<List>) ကို &List လိုတဲ့နေရာမှာ ပေးလို့ ရပါတယ်။
  3. 1 → 2 → end list ကို ဆောက်တာပါ။ list drop ဖြစ်တဲ့အခါ Box တစ်ခုစီက သူ့ node ကို အလှည့်ကျ free လုပ်ပါတယ်။
  4. Rc ("reference counted") က thread တစ်ခုတည်းထဲမှာ value တစ်ခုကို owner အများကြီး ရှိခွင့်ပြုပါတယ်။ JS object တွေ အလုပ်လုပ်ပုံနဲ့ အနီးစပ်ဆုံးပါ — နောက်ဆုံး owner ထွက်သွားမှ value ကို free လုပ်ပါတယ်။
  5. Rc::clone က string ကို မကူးပါဘူး — count ကို တစ်တိုးပြီး တူညီတဲ့ value ကို ညွှန်တဲ့ pointer နောက်တစ်ခု ပြန်ပေးပါတယ်။ (x.clone() အစား) Rc::clone(&x) လို့ ရေးတာက ဖတ်သူတွေကို အဲဒါ ထင်ရှားစေပါတယ်။
  6. အခု owner သုံးယောက်ပါ။ Shared ဖြစ်ပြီး mutable ဖြစ်တာက borrow rule တွေ တားမြစ်တာ အတိအကျ ဖြစ်လို့ Rc ထဲက value တွေက read-only ပါ။
  7. RefCell က borrow စစ်ဆေးမှုကို compile time ကနေ run time ဆီ ရွှေ့ပါတယ် — shared handle ကနေ အထဲက အကြောင်းအရာကို ပြင်ခွင့်ရပြီး mutable borrow နှစ်ခု ထပ်တာနဲ့ panic ဖြစ်ပါတယ်။ Rc<RefCell<T>> က thread တစ်ခုထဲမှာ shared mutable state အတွက် ပုံမှန် ချက်နည်းပါ။
  8. borrow_mut() က statement အဆုံးမှာ ပြီးဆုံးတဲ့ ယာယီ mutable borrow တစ်ခု ပေးပါတယ်။
  9. borrow() က ယာယီ shared borrow ပါ။ ဒီ type တွေကို နည်းနည်းပဲ သုံးပါ — Rust code အများစုက ဘယ်ဟာမှ မလိုပါဘူး၊ နေရာတိုင်းမှာ Rc<RefCell<…>> ကို ကိုင်နေရတာက ownership နဲ့ ဆန့်ကျင်နေတဲ့ ဒီဇိုင်းရဲ့ လက္ခဏာ ဖြစ်တတ်ပါတယ်။

မကြောက်ရတဲ့ concurrency — thread၊ channel နဲ့ lock

JavaScript မှာ event loop တစ်ခုစီအတွက် thread တစ်ခုပဲ ရှိပြီး default အနေနဲ့ ဘာမှ မမျှသုံးတဲ့ Web Worker တွေ ပေးပါတယ်။ Rust က memory မျှသုံးနိုင်တဲ့ တကယ့် OS thread တွေ ပေးပြီး တူညီတဲ့ ownership rule တွေ က data race တွေကို compile time မှာ တားပါတယ်။ Marker trait နှစ်ခုက အလုပ်လုပ်ပါတယ် — Send (တခြား thread ဆီ ရွှေ့လို့ safe) နဲ့ Sync (thread တွေကြား မျှသုံးလို့ safe)။ ဥပမာ compiler က Rc ကို thread ကျော်ပြီး ပို့တာကို ငြင်းပါတယ်။

rustပြင်ပြီး Run ကို နှိပ်ကြည့်ပါ

Tab က indent · ⌘/Ctrl + Enter က run · Esc က editor ထဲက ထွက်

တစ်ကြောင်းချင်း ရှင်းလင်းချက်

  1. Id တစ်ခုစီအတွက် thread တစ်ခုစီ စုစုပေါင်း သုံးခု စပြီး သူတို့ handle တွေကို သိမ်းထားပါတယ်။ collect() က အရေးကြီးပါတယ် — iterator တွေက lazy ဖြစ်လို့ မပါရင် thread တစ်ခုမှ နောက်မကျမချင်း မစပါဘူး။
  2. thread::spawn က closure တစ်ခုကို OS thread အသစ်ပေါ်မှာ run ပါတယ်။ Thread က လက်ရှိ function ထက် ပိုကြာကြာ ရှင်နိုင်လို့ borrow လုပ်တာ safe မဖြစ်တဲ့အတွက် move က closure ကို သူသုံးတဲ့ အရာ (id) ရဲ့ ownership ကို ယူစေပါတယ်။
  3. join() က promise ကို await လုပ်သလို thread ကို စောင့်ပြီး သူ့ result ကို ပြန်ပေးပါတယ်။ Thread က panic ဖြစ်ခဲ့နိုင်လို့ Result ဖြစ်ပါတယ်။
  4. Counter မျှသုံးဖို့ — Mutex က value ကို တစ်ကြိမ်မှာ thread တစ်ခုပဲ ထိနိုင်အောင် စောင့်ပေးပြီး Arc ("atomic reference counted") က thread တိုင်းကို handle ပိုင်ခွင့်ပေးတဲ့ thread-safe Rc ပါ။
  5. Thread တစ်ခုစီက ရွှေ့ယူဖို့ ကိုယ်ပိုင် Arc clone တစ်ခု ရပါတယ်။ Data ကိုယ်တိုင်ကို မကူးပါဘူး။
  6. lock() က mutex ကို စောင့်ပြီး value ဆီ dereference ဖြစ်တဲ့ guard တစ်ခု ပြန်ပေးပါတယ်။ Guard drop ဖြစ်တဲ့အခါ (ဒီမှာ statement အဆုံး) lock ကို လွှတ်လို့ unlock လုပ်ဖို့ မေ့လို့ မရပါဘူး။ Arc<Mutex<i32>> ကို thread တိုင်းက ဖမ်းယူတဲ့ ရိုးရိုး let mut counter = 0 နဲ့ အစားထိုးကြည့်ပါ — race ကြောင့် update တွေ ပျောက်အောင် ခွင့်မပြုဘဲ compiler က ငြင်းပါတယ်။
  7. အမြဲ 8000 ပါ။ C မှာ synchronise မလုပ်ထားတဲ့ thread တွေနဲ့ဆိုရင် ဘယ်ဂဏန်းမဆို ထွက်နိုင်ပါတယ် — Rust မှာတော့ အဲဒီ version က compile မဖြစ်ပါဘူး။
  8. Go ရဲ့ channel လို channel တစ်ခုပါ — tx က ပို့ပြီး rx က လက်ခံပါတယ်။ mpsc ဆိုတာ ပို့သူအများ၊ လက်ခံသူတစ်ယောက် လို့ အဓိပ္ပာယ်ရလို့ thread တစ်ခုစီက tx ရဲ့ clone တစ်ခုစီ ရပါတယ်။
  9. ပို့သူ အားလုံး ပျောက်သွားမှ လက်ခံတဲ့ loop ပြီးဆုံးပါတယ်။ Worker thread တွေက ပြီးတဲ့အခါ သူတို့ clone တွေကို drop လုပ်ပြီး ဒီစာကြောင်းက မူရင်းကို drop လုပ်ပါတယ်။
  10. rx.iter() က channel မပိတ်မချင်း message တစ်ခုချင်းအတွက် စောင့်ပါတယ်။ Thread တွေက ဘယ်အစဉ်နဲ့မဆို ပြီးနိုင်လို့ တည်တည်ငြိမ်ငြိမ် print ထုတ်ဖို့ message တွေကို sort လုပ်ပါတယ်။
  11. Scoped thread တွေက move ဒါမှမဟုတ် Arc မပါဘဲ local data (data) ကို borrow လုပ်နိုင်ပါတယ်၊ thread::scope က return မပြန်ခင် အားလုံးကို စောင့်လို့ သူတို့ borrow လုပ်တဲ့အရာထက် ပိုမကြာနိုင်လို့ပါ။

Tokio နဲ့ Async Rust

Thread တွေက CPU အလုပ်အတွက် ကောင်းပါတယ်။ Network connection ထောင်ချီအတွက်တော့ JS ကနေ ကောင်းကောင်း သိပြီးသား async ကို လိုချင်ပါတယ်။ Syntax က အတူတူနီးပါးဖြစ်ပြီး model မှာ ကွာခြားချက် နှစ်ခု ရှိပါတယ်။ Rust Future က lazy ပါ — await ဒါမှမဟုတ် spawn မလုပ်မချင်း ဘာမှ မလုပ်ပါဘူး၊ JS promise ကတော့ ဖန်တီးလိုက်တဲ့ အချိန်မှာပဲ စပါတယ်။ ပြီးတော့ built-in event loop မရှိပါဘူး — runtime crate တစ်ခု ထည့်ရပြီး အမြဲလိုလို Tokio ပါ။

ts
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));async function fetchUser(id: number) {  await sleep(100 * id);  return 'user ' + id;}const [a, b] = await Promise.all([fetchUser(1), fetchUser(2)]);

တစ်ကြောင်းချင်း ရှင်းလင်းချက်

  1. Promise.all က နှစ်ခုလုံးကို တစ်ပြိုင်နက် run ပြီး နှစ်ခုလုံးကို စောင့်ပါတယ် — စုစုပေါင်း 300 ms မဟုတ်ဘဲ 200 ms လောက်ပါ။
rustပြင်ပြီး Run ကို နှိပ်ကြည့်ပါ

Tab က indent · ⌘/Ctrl + Enter က run · Esc က editor ထဲက ထွက်

တစ်ကြောင်းချင်း ရှင်းလင်းချက်

  1. async fn က async function က Promise<string> ပြန်ပေးသလို String အစား Future<Output = String> ကို ပြန်ပေးပါတယ်။
  2. .await က postfix keyword (expression နောက်မှာ ရေးတာ) ဖြစ်လို့ chain တွေက ဘယ်ကနေ ညာကို ဖတ်ရပါတယ် — client.get(url).send().await?.json().await?။ Worker thread တစ်ခုလုံးကို block လုပ်မယ့် std::thread::sleep ကို ဘယ်တော့မှ မသုံးဘဲ Tokio ရဲ့ sleep ကို သုံးပါ။
  3. ဒီ attribute macro က Tokio runtime ကို စပြီး main ကို အဲဒီပေါ်မှာ run ပါတယ်။ Tokio က scheduler၊ timer၊ TCP နဲ့ file I/O၊ channel တွေကို ပေးပါတယ်။
  4. Async fn ကို ခေါ်တာက future ကို ဆောက်ရုံပဲ ဆောက်ပါတယ်။ ဒီမှာ ဘာမှ မဖြစ်ပါဘူး — timer မစ၊ drop လုပ်ရင် ရိုးရိုးလေး လွှင့်ပစ်ပါတယ်။ JS promise ဆိုရင် run နေပြီ ဖြစ်မှာပါ။
  5. join! က future နှစ်ခုလုံးကို လက်ရှိ task ထဲမှာ တစ်ပြိုင်နက် poll လုပ်ပြီး နှစ်ခုလုံးကို စောင့်ပါတယ် — Promise.all ပေါ့။ (ပထမ ပြီးတဲ့ တစ်ခုကို ယူတဲ့ select! လည်း ရှိပါတယ် — Promise.race ပေါ့။)
  6. tokio::spawn က future တစ်ခုကို လွတ်လပ်တဲ့ task အဖြစ် runtime ဆီ လွှဲပေးပြီး တခြား worker thread ပေါ်မှာ run နိုင်ပါတယ်၊ promise တစ်ခုကို await မလုပ်သေးဘဲ လွှတ်လိုက်သလိုပေါ့။ Task တွေက စျေးပေါလို့ server တစ်ခုမှာ သိန်းချီ ရှိနိုင်ပါတယ်။
  7. JoinHandle ကို await လုပ်ရင် Result ရပါတယ် — task က panic ဖြစ်ခဲ့ရင် ဒါမှမဟုတ် cancel ခံရရင် Err ပါ။
  8. timeout က ဘယ် future ကိုမဆို deadline နဲ့ ထုပ်ပြီး အချိန်မီ မပြီးရင် Err(Elapsed) ပြန်ပေးပါတယ်။ 500 ms ကြာမယ့် fetch ကို 50 ms မှာ drop (cancel) လုပ်ပါတယ်။ Rust မှာ future ကို drop လုပ်တာက cancel လုပ်ပုံပါ။
  9. Run တစ်ခုလုံး 550 ms လောက် ကြာပါတယ်။ အစဉ်လိုက် run ရင် အဲဒီ await တွေက 900 ms ကျော် ကြာပါလိမ့်မယ်။

serde နဲ့ JSON

TypeScript မှာ JSON.parse လုပ်ပြီး Zod နဲ့ ပုံစံကို စစ်တာမျိုး လုပ်နိုင်ပါတယ်။ Rust မှာတော့ serde က parser ကို သင့် struct ကနေ derive လုပ်ပေးလို့ parse လုပ်ရင်းနဲ့ ပုံစံကို စစ်ပြီး type ပါတဲ့ value ဒါမှမဟုတ် တိကျတဲ့ error တစ်ခု ရပါတယ်။ Ecosystem ထဲမှာ အသုံးအများဆုံး crate တွေထဲက တစ်ခုပါ။

rustပြင်ပြီး Run ကို နှိပ်ကြည့်ပါ

Tab က indent · ⌘/Ctrl + Enter က run · Esc က editor ထဲက ထွက်

တစ်ကြောင်းချင်း ရှင်းလင်းချက်

  1. Serialize နဲ့ Deserialize ကို derive လုပ်တာက JSON code ကို compile time မှာ ထုတ်ပေးပါတယ်။ Run နေချိန်မှာ reflection မဖြစ်လို့ serde က အရမ်းမြန်ပါတယ်။
  2. Rust field တွေက snake_case ဖြစ်ပြီး JSON က camelCase ဖြစ်တတ်ပါတယ် — ဒီ attribute က display_name ကို "displayName" နဲ့ ချိတ်ပေးပါတယ်။
  3. Option field က TS မှာ email?: string | null လို null ဒါမှမဟုတ် key မပါတာကို လက်ခံပါတယ်။
  4. #[serde(default)] က မပါတဲ့ key ကို type ရဲ့ default ဖြစ်တဲ့ ဗလာ Vec နဲ့ ဖြည့်ပါတယ်။
  5. main ကိုယ်တိုင်က Result ပြန်နိုင်လို့ ? က အထဲမှာ အလုပ်လုပ်ပါတယ်။ Err ဆိုရင် error ကို print ထုတ်ပြီး fail exit code နဲ့ program ပြီးဆုံးပါတယ်။
  6. Raw string r#"…"# က အထဲက quote တွေကို escape လုပ်စရာ မလိုလို့ JSON နဲ့ regex တွေအတွက် အဆင်ပြေပါတယ်။
  7. User ထဲကို တိုက်ရိုက် parse လုပ်ပါတယ်။ Type annotation က ဘာအဖြစ် parse လုပ်ရမလဲ ရွေးပါတယ် — ဒါက Zod schema နဲ့ TS type ကို တစ်ခုတည်းမှာ ပေါင်းထားတာပါ။
  8. Type မှားတာကို parse လုပ်ရင်းနဲ့ input ထဲက နေရာနဲ့တကွ ဖမ်းပါတယ်။ unwrap_err() က error ကို ထုတ်ယူပါတယ် (parse အောင်မြင်ခဲ့ရင် panic ဖြစ်ပါလိမ့်မယ်)။
  9. Serialise လုပ်တာက ပြောင်းပြန် လမ်းကြောင်းပါ။ Struct update syntax က tag တစ်ခုပါတဲ့ user ရဲ့ ပြင်ထားတဲ့ copy တစ်ခု လုပ်ပါတယ်။
  10. serde_json::Value က TS ရဲ့ unknown လို ပုံစံ မသိတဲ့ JSON အတွက်ပါ။ မရှိတဲ့ key ကို index လုပ်ရင် panic မဖြစ်ဘဲ null ရပါတယ်။

unsafe အကြောင်း အကျဉ်းချုပ်

အခုထိ အားလုံးက safe Rust ပါ — dangling pointer နဲ့ data race မရှိကြောင်း compiler က သက်သေပြပါတယ်။ unsafe block ထဲမှာ အပိုအရာ ငါးခု လုပ်နိုင်ပြီး အဓိကက raw pointer တွေကို dereference လုပ်တာနဲ့ C function တွေကို ခေါ်တာပါ။ Compiler က ကျန်တာအားလုံးကို ဆက်စစ်ပါတယ် — unsafe ဆိုတာ "ဒီလုပ်ဆောင်ချက် အနည်းငယ် valid ဖြစ်ကြောင်း programmer ကျွန်တော် အာမခံပါတယ်" လို့ပဲ အဓိပ္ပာယ်ရပါတယ်။

rustပြင်ပြီး Run ကို နှိပ်ကြည့်ပါ

Tab က indent · ⌘/Ctrl + Enter က run · Esc က editor ထဲက ထွက်

တစ်ကြောင်းချင်း ရှင်းလင်းချက်

  1. Raw pointer *mut i32 က C-style pointer ပါ။ ဖန်တီးတာက safe ပါ — compiler မစစ်နိုင်တာက အဲဒီကနေ ဖတ်တာ ဒါမှမဟုတ် ရေးတာပါ။
  2. Dereference လုပ်တာကို unsafe ထဲမှာ လုပ်ရပါမယ်။ Block က reviewer ကြည့်ရမယ့် နေရာကို အတိအကျ မှတ်ပေးပါတယ်။
  3. get_unchecked က v[2] လုပ်တဲ့ bounds check ကို ကျော်ပါတယ်။ အဆုံးကျော်တဲ့ index နဲ့ဆိုရင် undefined behaviour ဖြစ်လို့ unsafe ဖြစ်ရတာပါ။ ဒီလို နည်းလမ်းတွေ မသုံးခင် တိုင်းတာပါ — မလိုကြောင်း သက်သေပြနိုင်တဲ့ bounds check တွေကို optimiser က များသောအားဖြင့် ဖယ်ပေးပါတယ်။ Application code မှာ unsafe ကို မလိုသလောက်ပါပဲ။

Project အသေးလေး — စကားလုံး အကြိမ်ရေ report

ဒါက အရင်က အရာအများစုကို သုံးထားတဲ့ program တစ်ခုပါ — constant တစ်ခု၊ struct တစ်ခု၊ trait implementation တစ်ခု၊ iterator pipeline တစ်ခု၊ HashMap တစ်ခု၊ ကိုယ်ပိုင် အစဉ်နဲ့ sort လုပ်တာ၊ ပြီးတော့ Result။ စာသား ဒါမှမဟုတ် limit ကို ပြောင်းပြီး run ကြည့်ပါ။

rustပြင်ပြီး Run ကို နှိပ်ကြည့်ပါ

Tab က indent · ⌘/Ctrl + Enter က run · Esc က editor ထဲက ထွက်

တစ်ကြောင်းချင်း ရှင်းလင်းချက်

  1. const string slice ပါ။ တကယ့် CLI မှာ ဒါက file (std::fs::read_to_string(path)?) ဒါမှမဟုတ် standard input ကနေ လာပါလိမ့်မယ်။
  2. Result type — စုစုပေါင်းနဲ့ ထိပ်ဆုံး စကားလုံးတွေကို သူတို့ count တွေနဲ့ (String, usize) tuple တွေအဖြစ်ပါ။
  3. Display ကို implement လုပ်တာက report ကို toString() ကို override လုပ်သလို {} နဲ့ ကိုယ်တိုင် print ထုတ်စေပါတယ်။ writeln! က formatter ထဲကို စာကြောင်းတစ်ကြောင်း ရေးပြီး ? က write error ရှိရင် လွှဲပေးပါတယ်။
  4. Format spec တွေ — {:>2} က column နှစ်ခုမှာ ညာဘက်ကပ်ပြီး {word:<6} က ခြောက်ခုမှာ ဘယ်ဘက်ကပ်လို့ bar တွေ တန်းညီပါတယ်။ "#".repeat(n) က JS ရဲ့ "#".repeat(n) လိုပဲ bar ကို ဆောက်ပါတယ်။ *n က iterator ပေးတဲ့ &usize ကို dereference လုပ်ပါတယ်။
  5. စကားလုံးကနေ count ဆီ ချိတ်ထားတဲ့ owned map တစ်ခု ပြန်ပေးပါတယ်။ Map ရဲ့ type တွေကို အောက်မှာ သုံးပုံကနေ infer လုပ်ပါတယ်။
  6. စာလုံးမဟုတ်တဲ့ ဘာပေါ်မှာမဆို ခွဲတာက space၊ full stop နဲ့ comma တွေကို တစ်ခါတည်း ကိုင်တွယ်ပါတယ်။
  7. str::to_lowercase က method ကို function အဖြစ် ပေးတာပါ၊ .map(s => s.toLowerCase()) လိုပေမဲ့ ပိုတိုပါတယ်။
  8. ဗလာ input အတွက် error ပါ။ Program အသေးလေးမှာ ရိုးရိုး String ကို error type အဖြစ် သုံးလို့ ရပါတယ်။
  9. into_iter() က map ကို consume လုပ်ပြီး သူ့ key နဲ့ value တွေကို vector ထဲ move လုပ်လို့ ဘာမှ မကူးပါဘူး။ ဒီနောက် counts ကို သုံးလို့ မရတော့ပါဘူး။
  10. Count နဲ့ အများဆုံးကနေ sort လုပ်ပြီး (a မတိုင်ခင် b) တူနေရင် then_with နဲ့ အက္ခရာစဉ်အလိုက် ခွဲပါတယ်။ cmp က Ordering (Less၊ Equal ဒါမှမဟုတ် Greater) ပြန်ပေးပြီး comparator ရဲ့ အနုတ်၊ သုည ဒါမှမဟုတ် အပေါင်း ဂဏန်းရဲ့ Rust version ပါ။
  11. ရလဒ်နှစ်မျိုးလုံးကို ကိုင်တွယ်ပါ။ eprintln! က console.error လို standard error ဆီ ရေးပါတယ်။

Browser ထဲက Rust — WebAssembly

ဒီနေရာက Rust နဲ့ သင့်ရှိပြီးသား stack ဆုံတဲ့ နေရာပါ။ Rust က WebAssembly ဆီ compile ဖြစ်ပြီး wasm-bindgen က JS glue code နဲ့ TypeScript type တွေကို ထုတ်ပေးလို့ TS app တစ်ခုက Rust function တွေကို npm module တစ်ခုလို ခေါ်နိုင်ပါတယ်။ Browser ထဲက အလုပ်လေးတွေ — image နဲ့ video processing၊ parser၊ compression၊ game၊ cryptography — အတွက် သင့်တော်ပါတယ်။

rust
use wasm_bindgen::prelude::*; #[wasm_bindgen]pub fn slugify(title: &str) -> String {    title.to_lowercase().split_whitespace().collect::<Vec<_>>().join("-")}

တစ်ကြောင်းချင်း ရှင်းလင်းချက်

  1. cargo add wasm-bindgen ပြီးနောက် prelude က attribute နဲ့ JS interop type တွေကို ယူလာပါတယ်။ Cargo.toml ထဲမှာ crate က [lib] အောက်မှာ crate-type = ["cdylib"] လည်း လိုပါတယ်။
  2. #[wasm_bindgen] က function ကို JavaScript ဆီ export လုပ်ပြီး JS နဲ့ Wasm memory ကြား string တွေ ကူးပေးတဲ့ code ကို ထုတ်ပေးပါတယ်။
sh
rustup target add wasm32-unknown-unknowncargo build --release --target wasm32-unknown-unknownwasm-bindgen --target web --out-dir pkg target/wasm32-unknown-unknown/release/slug.wasm

တစ်ကြောင်းချင်း ရှင်းလင်းချက်

  1. WebAssembly target ကို တစ်ခါ install လုပ်ပြီး crate ကို .wasm file အဖြစ် build လုပ်ပါတယ်။
  2. wasm-bindgen CLI (cargo install wasm-bindgen-cli) က pkg/slug.js၊ pkg/slug.d.ts နဲ့ .wasm file ကို ရေးပေးပါတယ်။ Vite လို bundler တွေက တိုက်ရိုက် import လုပ်နိုင်ပါတယ်။
ts
import init, { slugify } from './pkg/slug.js'; await init();console.log(slugify('Zero to Hero in Rust')); // "zero-to-hero-in-rust"

တစ်ကြောင်းချင်း ရှင်းလင်းချက်

  1. ရိုးရိုး ES module import ပါ။ ထုတ်ပေးထားတဲ့ .d.ts file က slugify ကို (title: string) => string type ပေးလို့ editor က autocomplete လုပ်ပေးပါတယ်။
  2. init() က .wasm file ကို တစ်ခါ download လုပ်ပြီး instantiate လုပ်ပါတယ်။
  3. ဒီကစပြီး ရိုးရိုး function call ပါပဲ။ Rust code က browser ရဲ့ sandbox ထဲမှာ native နီးပါး အမြန်နှုန်းနဲ့ run ပါတယ်။

Rust နဲ့ ဘာတွေ ဆောက်နိုင်လဲ

  • Command-line tool တွေ။ clap က argument တွေကို parse လုပ်ပြီး ရလဒ်က မြန်တဲ့ binary တစ်ခုတည်းပါ။ ripgrep၊ bat၊ fd၊ eza၊ starship နဲ့ uv (Python package manager) အားလုံး Rust ပါ။
  • Web backend နဲ့ API တွေ။ Axum (Tokio team က)၊ Actix Web နဲ့ Rocket ကို database အတွက် SQLx ဒါမှမဟုတ် Diesel နဲ့ တွဲသုံးပါတယ်။ Service တွေက memory နည်းနည်းပဲ သုံးပြီး latency အရမ်း တည်ငြိမ်ပါတယ်။
  • JavaScript tooling။ ခေတ်သစ် JS toolchain ရဲ့ အများစုက အခု Rust ပါ — SWC (Next.js နောက်က compiler)၊ Turbopack (Next.js 16 ရဲ့ default bundler)၊ Rspack၊ Rolldown နဲ့ Oxc (Vite team ရဲ့ bundler နဲ့ parser)၊ Biome (formatter နဲ့ linter)၊ Lightning CSS နဲ့ Deno runtime။
  • Browser၊ edge platform (Cloudflare Workers က JS ဘေးမှာ Wasm ကို run ပါတယ်) နဲ့ plugin system တွေအတွက် WebAssembly။
  • Desktop နဲ့ mobile app တွေ။ Tauri က web front end (React၊ Svelte၊ …) ကို Rust back end နဲ့ ပေးပြီး app တွေက Electron ထက် အများကြီး သေးပါတယ်။
  • Systems software — operating-system အစိတ်အပိုင်းတွေ (Linux kernel driver၊ Windows နဲ့ Android ရဲ့ အစိတ်အပိုင်းတွေ)၊ database၊ proxy (Cloudflare ရဲ့ Pingora)၊ virtual machine (AWS Firecracker)။
  • Embedded နဲ့ IoT။ no_std Rust က embassy async framework နဲ့ chip တစ်ခုစီအတွက် HAL crate တွေကနေတစ်ဆင့် operating system မပါတဲ့ microcontroller တွေပေါ်မှာ run ပါတယ်။
  • Game၊ graphics နဲ့ data။ Bevy engine၊ platform တိုင်းမှာ GPU အလုပ်အတွက် wgpu၊ ပြီးတော့ pandas ထက် အများကြီး မြန်တဲ့ DataFrame library ဖြစ်တဲ့ Polars။

အားသာချက်နဲ့ အားနည်းချက်

Rust ထူးချွန်တဲ့ နေရာတွေ

  • Garbage collector မပါတဲ့ memory safety။ Android vulnerability အားလုံးထဲမှာ memory-safety bug တွေ ပထမဆုံးအကြိမ် 20% အောက် ကျသွားကြောင်း၊ သူတို့ Rust code မှာ C နဲ့ C++ ထက် ဒီလို bug တစ်ထောင်ဆလောက် နည်းကြောင်း Google က ၂၀၂၅ နိုဝင်ဘာမှာ report လုပ်ခဲ့ပါတယ်။
  • အမြန်နှုန်းနဲ့ ကြိုခန့်မှန်းနိုင်မှု။ Performance က C နဲ့ C++ နဲ့ တန်းတူဖြစ်ပြီး GC မရှိလို့ pause-time spike တွေ မရှိပါဘူး — Discord နဲ့ Figma က service တွေကို ပြောင်းရွှေ့ခဲ့တာ ဒါကြောင့် အတိအကျပါ။
  • "Compile ဖြစ်ရင် များသောအားဖြင့် အလုပ်လုပ်တယ်။" Type system၊ Option၊ Result နဲ့ exhaustive match က ဘာမှ မ run ခင် bug အမျိုးအစား တစ်ခုလုံးကို ဖမ်းလို့ codebase ကြီးကြီးကို refactor လုပ်ရတာ အများကြီး ကြောက်စရာ နည်းပါတယ်။
  • မကြောက်ရတဲ့ concurrency။ Data race တွေက compile error တွေပါ။
  • အလွန်ကောင်းတဲ့ tooling။ Cargo၊ rustfmt၊ Clippy၊ rust-analyzer နဲ့ built-in test နဲ့ doc (cargo doc) တွေက install တိုင်းမှာ ပါပြီး project တိုင်းမှာ တစ်ပုံစံတည်း အလုပ်လုပ်ပါတယ်။
  • သင်ပေးတဲ့ compiler message တွေ။ Error တွေက ဘာမှားလဲ ရှင်းပြပြီး များသောအားဖြင့် ပြင်နည်းကို အကြံပြုပါတယ်။
  • နေရာတိုင်းလိုလို run ပါတယ် — server၊ Wasm ကနေတစ်ဆင့် browser၊ ဖုန်း၊ microcontroller၊ dependency မပါတဲ့ binary တစ်ခုတည်းအဖြစ်ပါ။

နာကျင်ရတဲ့ နေရာတွေ

  • မတ်စောက်တဲ့ learning curve။ Ownership၊ borrowing နဲ့ lifetime တွေ သဘာဝကျလာဖို့ ရက်သတ္တပတ်တွေ ကြာပါတယ်။ "Borrow checker နဲ့ တိုက်ရတဲ့" ကာလတစ်ခုကို မျှော်လင့်ထားပါ။ အဲဒါ ပြီးဆုံးသွားပြီး ချန်ထားခဲ့တဲ့ အလေ့အထတွေက ဘယ်ဘာသာစကားမှာမဆို ပိုကောင်းတဲ့ programmer ဖြစ်စေပါတယ်။
  • Compile time နှေးတယ်။ Monomorphisation နဲ့ LLVM optimisation က အချိန်ကုန်ပါတယ်။ Project ကြီးတစ်ခုရဲ့ clean build က မိနစ်တွေ ကြာနိုင်ပါတယ်။ cargo check၊ incremental build နဲ့ crate တွေ ခွဲတာက ကူညီပါတယ်။
  • Prototype လုပ်ဖို့ နှေးတယ်။ Script မြန်မြန် ဒါမှမဟုတ် စမ်းသပ်မှုတစ်ခုအတွက် TypeScript ဒါမှမဟုတ် Python က ပိုမြန်မြန် ရောက်စေပါတယ်။ Rust က error နဲ့ edge case တိုင်းကို အစကတည်းက ကိုင်တွယ်ခိုင်းပါတယ်။
  • Async မှာ ကြမ်းတမ်းတဲ့ အစွန်းတွေ ရှိတယ်။ Pinning၊ future တွေပေါ်က Send bound တွေနဲ့ runtime ရွေးတာက JS ရဲ့ async ထက် ခက်ပြီး အဲဒီမှာ error message တွေက အဖော်ရွေမှု အနည်းဆုံးပါ။
  • တချို့ နယ်ပယ်တွေမှာ ecosystem က ငယ်သေးတယ်။ Web back end နဲ့ CLI တွေက ရင့်ကျက်ပြီး GUI toolkit၊ တချို့ enterprise SDK နဲ့ ML training ကတော့ JS၊ Python ဒါမှမဟုတ် Java ထက် ပါးပါတယ်။
  • ဝန်ထမ်းခန့်ခြင်းနဲ့ team ရဲ့ အရှိန်ယူချိန်။ Rust ကို ကောင်းကောင်း သိတဲ့ developer က TS ဒါမှမဟုတ် Go ထက် နည်းလို့ အသုံးပြုမယ့် team က လေ့ကျင့်ဖို့ အချိန်လိုပါတယ်။

Rust၊ Go နဲ့ TypeScript ကို ဘေးချင်းယှဉ်

ဒီသုံးခုက ကုမ္ပဏီတစ်ခုတည်းမှာ အတူတူ ရှိတတ်လို့ တစ်ခုစီက ဘာမှာ အကောင်းဆုံးလဲ သိထားရင် အထောက်အကူ ဖြစ်ပါတယ်—

  • Memory။ TS (V8) နဲ့ Go က garbage-collected ဖြစ်ပြီး Rust က ownership သုံးကာ GC မရှိပါဘူး။ Go ရဲ့ GC pause တွေက တိုပေမဲ့ load များတဲ့အခါ tail latency မှာ ပေါ်လာနေဆဲပါ။
  • Concurrency။ TS — async/await ပါတဲ့ event loop တစ်ခု။ Go — ဘာသာစကားထဲ ပါပြီးသား goroutine နဲ့ channel၊ သုံးခုထဲမှာ အရိုးရှင်းဆုံး model။ Rust — thread နဲ့ async၊ data race တွေကို compile time မှာ ပယ်ထားပါတယ်။
  • Type system။ TS — structural ဖြစ်ပြီး အားကောင်းပေမဲ့ ဖယ်ပစ်ထားပြီး ဒီဇိုင်းအရ unsound ဖြစ်ပါတယ် (any)။ Go — ရိုးရှင်း၊ nominal၊ 1.18 ကစပြီး generic ပါပြီး feature နည်းနည်းပဲ တမင်ထားပါတယ်။ Rust — ကြွယ်ဝပြီး (data ပါတဲ့ enum၊ trait၊ generic၊ lifetime) sound ဖြစ်ပါတယ်။
  • Error တွေ။ TS — exception။ Go — if err != nil return value။ Rust — ? ပါတဲ့ Result၊ Go လို ထင်ရှားပေမဲ့ အများကြီး ပိုတိုပါတယ်။
  • Learning curve။ Go က သင်ယူဖို့ အလွယ်ဆုံး (weekend တစ်ခု)၊ JS သိရင် TS က ဒုတိယ၊ Rust က အခက်ဆုံး (ရက်သတ္တပတ်တွေကနေ လတွေအထိ)။
  • အကောင်းဆုံး နယ်ပယ်။ TS — UI၊ full-stack web app နဲ့ browser နဲ့ server ကြား code မျှသုံးတာ။ Go — network service၊ CLI နဲ့ cloud infrastructure ကို team သေးသေးနဲ့ မြန်မြန် (Docker၊ Kubernetes နဲ့ Terraform ကို Go နဲ့ ရေးထားပါတယ်)။ Rust — performance အရေးကြီးတဲ့ လမ်းကြောင်း၊ systems software၊ tooling၊ embedded နဲ့ Wasm။

ကုမ္ပဏီကြီးတွေက TypeScript/JavaScript၊ Go နဲ့ Rust ကို ဘယ်လို တွဲသုံးလဲ

ကုမ္ပဏီကြီးတွေက ဘာသာစကား တစ်ခုတည်းကို ရွေးလေ့ မရှိပါဘူး။ အဖြစ်များတဲ့ ပုံစံက product မျက်နှာစာအတွက် TypeScript၊ network service နဲ့ infrastructure အများစုအတွက် Go၊ ပြီးတော့ latency၊ memory ဒါမှမဟုတ် safety အရေးအကြီးဆုံး နေရာမှာ Rust ပါ။ မှတ်တမ်းကောင်းကောင်း ရှိတဲ့ ဥပမာ တချို့—

  • Discord။ Desktop နဲ့ web client တွေက JavaScript (React၊ desktop မှာ Electron) ပါ။ User တိုင်း ဘာဖတ်ပြီးပြီလဲ မှတ်ထားတဲ့ Read States service ကို Go နဲ့ ရေးခဲ့ပြီး Go ရဲ့ garbage collector က နှစ်မိနစ်လောက်တစ်ခါ latency spike တွေ ဖြစ်စေလို့ Rust ဆီ ပြောင်းခဲ့ပါတယ်။ Optimise မလုပ်ရသေးတဲ့ ပထမဆုံး Rust version ကတောင် tune လုပ်ထားတဲ့ Go version ထက် ပိုကောင်းခဲ့ပါတယ်။
  • Figma။ Editor UI က TypeScript ဖြစ်ပြီး rendering engine က WebAssembly ဆီ compile လုပ်ထားတဲ့ C++ ပါ။ Document တွေကို sync လုပ်တဲ့ multiplayer server က TypeScript နဲ့ စခဲ့ပြီး garbage-collection pause တွေကြောင့် တစ်စိတ်တစ်ပိုင်း မလိုက်နိုင်တော့တဲ့အခါ Rust နဲ့ ပြန်ရေးခဲ့ပါတယ်။ Go နဲ့ ရေးထားတဲ့ ကျယ်ကျယ်ပြန့်ပြန့် သုံးနေတဲ့ esbuild bundler ကို Figma ရဲ့ ပူးတွဲတည်ထောင်သူ Evan Wallace က ဖန်တီးခဲ့တာပါ။
  • Microsoft။ TypeScript 7 (၂၀၂၆) က JavaScript အခြေခံ compiler ကို Go နဲ့ ရေးထားတဲ့ native compiler နဲ့ အစားထိုးခဲ့ပြီး project ကြီးတွေမှာ ဆယ်ဆလောက် ပိုမြန်ပါတယ်။ Go ရဲ့ ဖွဲ့စည်းပုံနဲ့ garbage collection က ရှိပြီးသား code ကို စာကြောင်းအလိုက်နီးပါး port လုပ်နိုင်စေလို့ team က Go ကို ရွေးခဲ့တာပါ။ Microsoft က Windows kernel ထဲမှာ Rust ကိုလည်း ထည့်သွင်းထားပြီး Azure ရဲ့ CTO က GC မပါတဲ့ project အသစ်တွေကို C/C++ အစား Rust သုံးဖို့ တိုက်တွန်းခဲ့ပါတယ်။
  • Cloudflare။ Developer တွေက Worker တွေကို JavaScript နဲ့ TypeScript (ဒါမှမဟုတ် Wasm ဆီ compile လုပ်ထားတဲ့ Rust) နဲ့ ရေးပါတယ်။ သူ့ HTTP proxy Pingora ကို NGINX ကို အစားထိုးဖို့ Rust နဲ့ ရေးခဲ့ပြီး တစ်ရက်ကို request တစ်ထရီလီယံကျော် ကိုင်တွယ်ကာ အခု open source ဖြစ်ပါပြီ။ Internal service အများကြီးကို Go နဲ့ ရေးထားပါတယ်။
  • Vercel။ Next.js က TypeScript ဖြစ်ပေမဲ့ အောက်က compiler က Rust ပါ — SWC က code ကို transform လုပ်ပြီး Rust ပဲ ဖြစ်တဲ့ Turbopack က Next.js 16 မှာ default bundler ဖြစ်လာခဲ့ပါတယ်။
  • Amazon Web Services။ AWS Lambda function တိုင်းနဲ့ Fargate task တိုင်းကို run တဲ့ micro-VM Firecracker ကို Rust နဲ့ ရေးထားပါတယ်။ Infrastructure-as-code toolkit ဖြစ်တဲ့ AWS CDK ကို TypeScript နဲ့ ရေးထားပြီး AWS က Go၊ JavaScript နဲ့ Rust အတွက် တရားဝင် SDK တွေ ထိန်းသိမ်းပါတယ်။
  • Google။ Go ကို ဖန်တီးခဲ့ပြီး သူ့ cloud တစ်ခုလုံးမှာ သုံးကာ Angular (TypeScript) က သူ့ front-end framework ပါ။ Android ရဲ့ low-level code အသစ်တွေကို Rust နဲ့ ရေးပြီး အပေါ်က security ရလဒ်တွေ ရခဲ့ပါတယ်။
  • Dropbox။ Desktop sync engine Nucleus ကို Rust နဲ့ ပြန်ရေးခဲ့ပြီး engine ကို test လုပ်ရ ပိုလွယ်အောင် type system ကို သုံးကာ "မမှန်တဲ့ state တွေကို ဒီဇိုင်းကနေ ဖယ်ရှား" ခဲ့ပါတယ်။
  • Linux kernel။ ၂၀၂၅ ဒီဇင်ဘာ Maintainers Summit မှာ Rust ကို experimental မဟုတ်တော့ကြောင်း ကြေညာခဲ့ပါတယ် — အခု C နဲ့အတူ kernel ရဲ့ အမြဲတမ်း အစိတ်အပိုင်း ဖြစ်ပါပြီ။

JS/TS developer တစ်ယောက်အတွက် သင်ခန်းစာ — ရွေးစရာ မလိုပါဘူး။ TypeScript ထူးချွန်တဲ့နေရာမှာ ဆက်ထားပြီး ရိုးရှင်းမြန်ဆန်တဲ့ service လိုရင် Go ကို၊ တကယ့် performance၊ memory ဒါမှမဟုတ် safety နံရံကို တိုက်မိရင် Rust ကို ကိုင်ပါ။ သင့် toolchain အများကြီးကို အခု Go နဲ့ Rust နဲ့ ရေးထားလို့ သူတို့ကို သင်ယူတာက နေ့တိုင်း သုံးနေတဲ့ tool တွေကို ဖတ်ပြီး ပါဝင်ကူညီနိုင်စေပါတယ်။

သုညကနေ Hero ဆီ သွားမယ့် လမ်းပြမြေပုံ

  1. ပထမ ရက်သတ္တပတ် — အခြေခံတွေ။ rustup နဲ့ install လုပ်၊ editor မှာ rust-analyzer ထည့်ပြီး ဒီစာမျက်နှာက ဥပမာတိုင်းကို run ကြည့်ကာ ပျက်သွားတဲ့အထိ တစ်ခုချင်း ပြင်ကြည့်ပါ။ The Rust Programming Language ("the book"၊ doc.rust-lang.org/book မှာ အခမဲ့) ရဲ့ အခန်း ၁–၆ ကို ဖတ်ပါ။
  2. ဒုတိယ ရက်သတ္တပတ် — တကယ့် ownership။ Rustlings လေ့ကျင့်ခန်းတွေ လုပ်ပါ။ Borrow checker က ညည်းတဲ့အခါ ဘာမှ မပြောင်းခင် error message တစ်ခုလုံးနဲ့ အကြံပြုထားတဲ့ ပြင်နည်းကို ဖတ်ပါ။
  3. တတိယ ရက်သတ္တပတ် — CLI tool တစ်ခု။ သင်သုံးနေတဲ့ Node script အသေးလေးတစ်ခုကို clap၊ serde နဲ့ anyhow သုံးပြီး Rust နဲ့ ပြန်ရေးပါ။ Clippy ကို warning မရှိဘဲ pass ဖြစ်အောင် လုပ်ပါ။
  4. စတုတ္ထ ရက်သတ္တပတ် — trait၊ generic နဲ့ testing။ Book ကို အခန်း ၁၁ အထိ ပြီးအောင် ဖတ်ပြီး သင့် CLI အတွက် unit test တွေ ရေးပါ။
  5. ဒုတိယလ — async နဲ့ web။ Tokio tutorial ကို လုပ်ပြီး Axum နဲ့ SQLx သုံးကာ JSON API အသေးလေးတစ်ခု ဆောက်ပြီး TypeScript front end ကနေ ခေါ်ပါ။
  6. တတိယလ — ပိုနက်နက် သွားပါ။ Concurrency (book အခန်း ၁၆)၊ smart pointer နဲ့ lifetime ကို အသေးစိတ်၊ ပြီးတော့ Jon Gjengset ရဲ့ Rust for Rustaceans။ တစ်ခုခုကို WebAssembly ဆီ compile လုပ်ပြီး React app ကနေ သုံးပါ။
  7. ဆက်သွားပါ။ ခြောက်ပတ်တစ်ခါ release note တွေကို ဖတ်၊ This Week in Rust ကို follow လုပ်ပြီး SWC၊ Biome ဒါမှမဟုတ် ripgrep လို သင်သုံးနေပြီးသား tool တွေရဲ့ source ကို ဖတ်ပါ။ အဲဒီထဲက တစ်ခုမှာ issue အသေးလေးတစ်ခု ပြင်ပေးပါ။

Rust က JavaScript က ကိုက်မခံရမချင်း လျစ်လျူရှုခွင့်ပေးထားတဲ့ ownership အကြောင်းကို အစကတည်းက စဉ်းစားခိုင်းပါတယ်။ Compiler က တင်းကျပ်ပေမဲ့ သင့်ဘက်မှာ ရှိပါတယ် — သူ ဖြတ်သန်းခွင့်ပေးပြီးတာနဲ့ ညဉ့်နက်ပိုင်း bug အမျိုးအစား တစ်ခုလုံး ပျောက်သွားပါတယ်။

— Field note

Filed underrusttypescriptjavascriptgosystems

Written byZin Min Htun (Wine Chanz)Senior Full-Stack Developer · Da Nang, VietnamSay hello at campMore entries